Futex (Fast Userspace Mutex) это примитив синхронизации в ядре Linux, который позволяет ждать регион памяти по какому-то значению с каким-то таймаутом. Из-за своего названия может показаться, что futex это на самом деле mutex, но это не так. Это было так в гораздо более ранней реализации, но теперь (Linux 2.6+) это просто примитив очереди ожидания (waitqueue), доступный для пользовательского пространства. Опция FUTEX_WAKE говорит: «Разбуди всех, кто ждет по этому адресу». FUTEX_WAIT говорит: «Когда я читаю ADDR, он содержит VALUE: если он все еще содержит это значение, когда вы смотрите на него, спите, пока кто-то не вызовет FUTEX_WAKE по этому адресу». А FUTEX_FD посылает все нотификации в файловый дескриптор.



Mutex из Futex можно сделать так: Два потока совместно используют некоторую память и соглашаются использовать её часть в качестве синхронизации (или блокировки). 1 означает разблокирован, 0 означает заблокирован, <0 означает заблокирован и кто-то ждет. Чтобы захватить блокировку, вы выполняете операцию atomic декремента: если он достигает 0, значит, это было 1, и у вас есть блокировка. В противном случае вы немного подождёте. Когда вы хотите снять блокировку, вы снова устанавливаете ее на 1. Это действительно быстро, если нет конфликта за блокировку и futex достаточно хорошо работает при маленьком количестве потоков борящихся за блокировку. Также ядро достаточно хитрым образом не уходит в своё пространство, когда нет contention на регион, но об этом в другом посте.



Futex используется в каждой реализации лока, в том числе glibc, abseil. Это действительно удобный интерфейс для имплементации медленных путей в локах, семафорах. Futex хоть и поменял то, как мы мыслим о локах в 2000х годах, к сожалению всё ещё существует достаточно много проблем. Некоторые из них такие:



0. Код вокруг Futex нереально сложный

1. Futex ещё всё умеет только ждать по 32 битному региону памяти, иногда это может вызывать проблемы в долгоживущих программах и некоторых имплементациях локов. Также может не подходить для embedded систем, где 32 битного адреса нет совсем

2. Futex не умеет давать подсказки по тому на какой NUMA стоит иметь регион

3. Futex не умеет ждать по многим регионам памяти одновременно



Последний недостаток как ни странно достаточно сильно бьёт по игровым движкам, например, в Windows есть WaitForMultipleObjects. Тот же wine и proton писали, что на Linux создаются большие проблемы из-за этого и им приходится из-за этого придумывать трюки с eventfd , чтобы демонстрировать то же поведение, из-за чего происходит большая нагрузка на файловые дескрипторы, которых ограниченное количество на Linux (движки репортят миллионы). В итоге получается, что некоторые игры просто не могут пойти на Linux из-за проблем синхронизации или работают медленно.



Valve, Collabora (продукты LibreOffice и тд) объединили свои силы в пропозал почти 2 года назад улучшить futex добавив futex2, где большинство упомянутых проблем решаются. У них получилось с помощью множества wait (futex_waitv) ускорить Beat Saber и Tomb Raider на 4%.



К сожалению, после года обсуждений оф. RFC оно не попало в Linux 5.15, но имеет все шансы попасть в Linux 5.16, почти ни у кого нет уже вопросов, и кажется консенсус обретён.



Я надеюсь, что выбор Arch Linux у Steam Deck от Valve был под влиянием некоторых таких изменений, Arch Linux обновляет своё ядро очень быстро и можно спокойно продолжать гнуть свою палку по переводу игр на Linux, получая сразу все фичи, что несомненно меня радует как и глубокие изменения, которые Valve делают в ядре, чтобы это произошло. Кто, если не Valve 🙂



[1] Futexes Are Tricky (советую прочитать, чтобы понять как пользоваться futex)

[2] Изначальный пропозал от Collabora и Valve

[3] Rethinking the futex API

[4] futex man page

[5] A New Futex2() System Call - André Almeida, Collabora (видео от основного контрибьютера)

[6] Linux 5.15 attempt, 5.16 attempt

[7] WIN32 WaitForMultipleObjects API

[8] Valve’s upcoming Steam Deck will be based on Arch Linux—not Debian

[9] Официальный RFC в ядро

[10] Glibc lock implementation

[11] C++20 atomic_wait/notify