В современной вычислительной среде, где многоядерные процессоры стали нормой, а параллельные вычисления — неотъемлемой частью эффективного использования аппаратных ресурсов, проблема синхронизации доступа к общим данным становится краеугольным камнем разработки производительных и надежных приложений. Без продуманных механизмов синхронизации многопоточные программы сталкиваются с такими явлениями, как гонки данных, взаимные блокировки и инверсия приоритетов, которые могут привести к некорректной работе или полному зависанию системы.
Операционные системы, такие как Linux, предоставляют разработчикам обширный арсенал инструментов для управления параллелизмом. Среди них особое место занимает futex (Fast userspace mutex) — низкоуровневый, но чрезвычайно мощный примитив синхронизации, который выступает строительным блоком для большинства высокоуровневых абстракций, таких как мьютексы, семафоры и условные переменные, используемых в пользовательском пространстве. Актуальность глубокого понимания futex для студента или аспиранта, специализирующегося на системном программировании, операционных системах или параллельных вычислениях, трудно переоценить. Это не просто механизм, это ключ к созданию высокопроизводительных, масштабируемых и отказоустойчивых систем, способных максимально эффективно использовать возможности современного оборудования.
Настоящая работа представляет собой всесторонний анализ futex в ядре Linux, от его зарождения и эволюции до внутренних механизмов работы, API системных вызовов, преимуществ и недостатков, а также современных тенденций развития, включая futex2 и futex_waitv(). Целью исследования является создание актуального, структурированного и академически выверенного материала, который послужит не только глубоким погружением в тему, но и практическим руководством для тех, кто стремится максимально эффективно использовать возможности ядра Linux для синхронизации в высокопроизводительных приложениях.
Концепция и эволюция Futex: От идеи до futex2
Что такое Futex: Определение и основные идеи
Futex — это не просто аббревиатура от «Fast userspace mutex»; это целая философия, воплощенная в низкоуровневом, легковесном примитиве синхронизации, который служит фундаментальной основой для построения более сложных и привычных программистам механизмов, таких как мьютексы, семафоры, условные переменные, блокировки чтения-записи и барьеры. Его основная идея заключается в предоставлении максимально эффективного способа синхронизации потоков в пользовательском пространстве, сводя к минимуму количество дорогостоящих обращений к ядру операционной системы. По своей сути, futex представляет собой гибридный механизм, большая часть операций которого при отсутствии конфликтов выполняется непосредственно в пользовательском пространстве, что радикально сокращает накладные расходы на переключение контекста и вызовы ядра. Ядро ОС вступает в игру только тогда, когда возникает реальный конфликт за ресурс и потоку действительно необходимо перейти в состояние ожидания. Это позволяет достичь беспрецедентной скорости в бескризисных сценариях, что является краеугольным камнем его эффективности. Как следствие, разработчики могут создавать высокопроизводительные библиотеки, не беспокоясь о постоянных переходах между пользовательским и системным пространствами.
История создания и интеграции в ядро Linux
Идея futex зародилась в недрах IBM в начале 2000-х годов. В 2002 году разработчики Hubertus Franke, Matthew Kirkwood, Ingo Molnár и Rusty Russell представили концепцию, которая обещала значительное повышение производительности синхронизации в многопоточных Linux-системах. Их предложение быстро нашло отклик в сообществе разработчиков ядра. Первая экспериментальная поддержка futex появилась в ядре Linux версии 2.5.7. Однако, как это часто бывает с новыми и революционными идеями, начальная семантика и API требовали доработки. Со временем, после тщательного анализа и усовершенствований, текущая, устоявшаяся семантика futex была окончательно интегрирована в ядро Linux, начиная с версии 2.5.40. Это событие стало поворотным моментом, поскольку futex быстро завоевал статус одного из самых важных низкоуровневых примитивов синхронизации, став основой для большинства высокопроизводительных библиотек.
Эволюция и ключевые улучшения: Robust и PI Futexes
После первичной интеграции, развитие futex не остановилось. По мере роста требований к надежности и предсказуемости многопоточных систем, особенно в критически важных приложениях и системах реального времени, стали появляться новые вызовы и модификации.
Одной из значимых вех стало появление «robust futexes». В традиционных механизмах синхронизации, если поток, владеющий блокировкой, аварийно завершался, это могло привести к перманентной блокировке ресурса, вызывая «зависание» всей системы или части приложения. «Robust futexes» были разработаны для решения этой проблемы. Концепция, над которой Инго Молнар начал работать в 2006 году, была призвана обеспечить корректную обработку ситуаций, когда владелец блокировки неожиданно выходит из строя. Это гарантирует, что даже при аварийном завершении потока, связанная с ним блокировка будет корректно освобождена, предотвращая зависание других ожидающих потоков. Поддержка «robust futexes» была интегрирована в ядро Linux к ноябрю 2009 года, существенно повысив надежность системных библиотек, построенных на его основе.
Другим важным улучшением стали «PI futexes» (Priority Inheritance futexes), предназначенные для борьбы с проблемой инверсии приоритетов. Инверсия приоритетов — это ситуация, когда высокоприоритетный поток оказывается заблокированным низкоприоритетным потоком, который владеет необходимым ресурсом. Если низкоприоритетный поток сам прерывается потоком со средним приоритетом, то высокоприоритетный поток вынужден ждать еще дольше, что недопустимо в системах реального времени. «PI futexes», также разработанные Инго Молнаром и Томасом Гляйкснером в 2006 году, реализуют механизм наследования приоритетов. Это означает, что при захвате ресурса низкоприоритетным потоком, за которым ожидает высокоприоритетный, низкоприоритетный поток временно наследует приоритет высокоприоритетного, что позволяет ему быстрее завершить свою критическую секцию и освободить ресурс. Патчи для поддержки FUTEX_CMP_REQUEUE_PI появились в ядрах версий 2.6.31-rt* и 2.6.32-rc* уже к апрелю 2009 года.
futex2 и futex_waitv(): Новые горизонты синхронизации
Со временем, по мере того как Linux эволюционировал и требования к синхронизации становились все более сложными, обнаружились ограничения оригинального API futex. Это привело к активным дискуссиям о необходимости его усовершенствования, кульминацией которых стал проект futex2.
futex2 — это не просто эволюционное развитие, а скорее переосмысление интерфейса futex, направленное на преодоление его изначальных ограничений. Одной из главных целей futex2 является поддержка функций, аналогичных WaitForMultipleObjects из Win32 API, что позволяет потоку ожидать срабатывания любого из нескольких futex-ов одновременно. Это крайне важно для построения более гибких и эффективных механизмов синхронизации в сложных многокомпонентных системах. Кроме того, futex2 призван обеспечить поддержку futex-ов различных размеров (8, 16, 32 или 64 бита), что может повысить эффективность на разных архитектурах, хотя на данный момент реализована только поддержка 32-битных значений.
Первым значимым шагом в реализации концепции futex2 стало добавление системного вызова futex_waitv() в ядро Linux 5.16 в ноябре 2021 года. Этот системный вызов позволяет потоку ожидать на массиве futex-ов, пробуждаясь при срабатывании любого из них. Он принимает в качестве аргумента структуру, содержащую val, uaddr и flags для каждого futex в массиве, что существенно расширяет возможности многообъектной синхронизации. futex_waitv() закрывает одну из «слепых зон» традиционного futex API, предлагая более мощный и гибкий механизм для сложных сценариев ожидания. Это демонстрирует постоянное стремление разработчиков ядра Linux адаптировать и улучшать ключевые системные компоненты в ответ на растущие потребности современных высокопроизводительных приложений.
Принципы работы и архитектура Futex: Механизмы пользовательского и ядерного пространств
Futex word: Атомарное целое в общей памяти
В основе архитектуры futex лежит концепция «futex word» — 32-битного атомарного целого числа, расположенного в общей памяти. Это число является центральным элементом, через который происходит координация между потоками и, при необходимости, между пользовательским и ядерным пространством. Важно отметить, что даже на 64-битных системах futex word остается 32-битным, а его адрес передается системному вызову futex().
Размещение futex word критически важно:
- Для совместного использования между процессами:
futex wordдолжно быть размещено в области разделяемой памяти, созданной, например, с помощьюmmap(2)илиshmat(2). Это позволяет разным процессам иметь доступ к одному и тому жеfutex wordи, соответственно, синхронизировать свою работу. - Для совместного использования в многопоточной программе: Достаточно разместить
futex wordв глобальной переменной или в динамически выделенной памяти, доступной всем потокам процесса.
Операции с этим числом (например, проверка, увеличение, уменьшение) всегда выполняются атомарно, обычно с использованием специализированных инструкций процессора, таких как compare-and-swap (CAS). Именно атомарность операций с futex word гарантирует целостность состояния блокировки в условиях параллельного доступа. Таким образом, futex word служит своеобразным «табло», через которое потоки сообщают друг другу о состоянии ресурса и координируют свои действия. Это позволяет эффективно координировать действия между независимыми сущностями, минимизируя накладные расходы.
Гибридная природа: Пользовательское пространство при отсутствии конфликтов
Одна из ключевых особенностей и главное преимущество futex заключается в его гибридной природе. Основной принцип проектирования futex — это минимизация обращений к ядру операционной системы. В идеальном, бескризисном сценарии (non-contended case), когда ресурс не заблокирован и поток может сразу получить к нему доступ, все операции с futex выполняются исключительно в пользовательском пространстве.
Процесс выглядит следующим образом:
- Проверка состояния: Поток, желающий захватить ресурс, сначала атомарно проверяет значение
futex wordв общей памяти. - Атомарный захват: Если
futex wordуказывает на свободный ресурс, поток пытается атомарно изменить его значение, чтобы захватить блокировку. - Успех в пользовательском пространстве: Если атомарная операция успешна, блокировка захвачена, и поток продолжает выполнение, не обращаясь к ядру. Это исключает дорогостоящие системные вызовы, переключения контекста и связанные с ними накладные расходы.
Такой подход значительно повышает производительность в ситуациях, когда конкуренция за ресурс низка или отсутствует. Атомарные инструкции процессора выполняются в масштабе наносекунд, тогда как системные вызовы могут занимать микросекунды, что для высокочастотных операций синхронизации дает колоссальный выигрыш.
Ядро ОС при конфликте: Очереди ожидания и переключение контекста
Ситуация меняется, когда возникает конфликт (contended case). Если поток пытается захватить ресурс, который уже заблокирован другим потоком, и атомарная операция в пользовательском пространстве не удается, то futex переключается на использование ресурсов ядра.
- Обращение к ядру: Поток выполняет системный вызов
futex()(например, с операциейFUTEX_WAIT). - Проверка условия ядром: Ядро проверяет значение
futex wordпо адресу, предоставленному потоком. Важно: Ядро блокирует поток только в том случае, если значениеfutex wordвсе еще соответствует ожидаемому значению, которое было предоставлено вызывающим потоком. Это предотвращает «ложные пробуждения» и «потерянные пробуждения». - Переход в состояние ожидания: Если условие для блокировки соблюдено (т.е. ресурс все еще заблокирован), ядро помещает поток в специальную очередь ожидания, которая внутренне ассоциирована с адресом
futex word. Поток переходит в спящий режим и освобождает процессорные ресурсы. - Пробуждение: Когда владелец ресурса освобождает его, он атомарно изменяет
futex wordи вызываетfutex()(например, с операциейFUTEX_WAKE), сигнализируя ядру о необходимости пробудить один или несколько потоков из очереди ожидания. Ядро выбирает потоки для пробуждения и переводит их в состояние готовности к выполнению.
Таким образом, ядро предоставляет централизованные очереди ожидания и механизм для безопасного перевода потоков в спящий режим и их последующего пробуждения, гарантируя корректность синхронизации даже в условиях высокой конкуренции.
Сравнение с традиционными механизмами синхронизации
Чтобы по-настоящему оценить инновационность и эффективность futex, необходимо сравнить его с другими, более традиционными механизмами синхронизации:
| Характеристика | Futex | Традиционные мьютексы/семафоры (системные) | Спинлоки (spinlock) |
|---|---|---|---|
| Основной принцип | Гибридный: User-space при отсутствии конфликта, kernel-space при конфликте. | Kernel-space: Каждая операция (блокировка/разблокировка) обычно требует системного вызова, даже при отсутствии конфликтов. | User-space или kernel-space: Активное ожидание (busy-wait), потребление CPU до освобождения. |
| Накладные расходы | Минимальные в бескризисных сценариях: Атомарные операции в пользовательском пространстве. | Высокие: Системные вызовы и переключение контекста для каждой операции. | Высокие: Постоянное потребление CPU, если ресурс занят. |
| Экономия CPU | Да: Ожидающий поток переводится в спящий режим в ядре, освобождая CPU. | Да: Ожидающий поток переводится в спящий режим. | Нет: Ожидающий поток активно «крутится», потребляя CPU. |
| Гибкость | Низкоуровневый строительный блок: Позволяет реализовывать любые высокоуровневые примитивы. | Высокоуровневые абстракции: Предоставляют готовые решения (например, POSIX мьютексы). | Низкоуровневый: Используется для коротких критических секций, где переключение контекста дороже «спиннинга». |
| Задержка | Низкая: Ядро задействуется только при конфликте. | Умеренная: Зависит от накладных расходов системного вызова. | Очень низкая: При кратковременном ожидании, так как нет переключения контекста. |
| Сложность использования | Высокая: Требует глубокого понимания атомарных операций и API ядра. | Умеренная: Высокоуровневые API более просты в использовании. | Средняя: Требует осторожности, чтобы не вызвать «голодание» CPU. |
| Использование в Linux | Основа для glibc/NPTL примитивов. |
Могут быть реализованы на основе futex или как отдельные системные вызовы. |
Используются в ядре и в очень специфических пользовательских сценариях. |
Архитектурная схема взаимодействия пользовательского и ядерного пространств при использовании Futex:
graph TD
subgraph Пользовательское Пространство (User Space)
A[Многопоточное Приложение/Библиотека] --> B{Попытка захвата<br>ресурса};
B -- Атомарная операция<br>на Futex Word (CAS) --> C{Ресурс свободен?};
C -- Да (бескризисный случай) --> D[Захват ресурса<br>без вызова ядра];
D --> E[Продолжение работы];
end
subgraph Ядро Linux (Kernel Space)
F[Очереди ожидания Futex<br>(Kernel Wait Queues)]
G[Обработчик системного вызова futex()]
end
C -- Нет (конфликт) --> H[Системный вызов futex(FUTEX_WAIT)];
H --> G;
G -- Проверка Futex Word --> I{Значение соответствует<br>ожидаемому?};
I -- Да --> J[Поток переходит в спящий режим<br>и добавляется в очередь F];
I -- Нет (например, ресурс<br>освобожден до блокировки) --> K[Возврат EWOULDBLOCK];
E -- Освобождение ресурса --> L[Атомарное изменение Futex Word];
L --> M[Системный вызов futex(FUTEX_WAKE)];
M --> G;
G -- Пробуждение потоков --> F;
F --> N[Пробужденные потоки<br>становятся готовыми к выполнению];
Пояснение к схеме:
На схеме показано, как futex работает в двух режимах:
- Бескризисный (User Space Path): Когда поток
Aпытается захватить ресурсB, он сначала выполняет атомарную операцию наFutex WordC. Если ресурс свободен, блокировкаDпроисходит полностью в пользовательском пространстве, и поток продолжает работуE. - Конфликтный (Kernel Space Path): Если ресурс занят
C, поток вызывает системный вызовfutex(FUTEX_WAIT)H, который обрабатывается ядромG. Ядро проверяетFutex WordIи, если ресурс все еще занят, переводит поток в спящий режим и помещает его в очередь ожиданияF. Когда владелец ресурса освобождает егоL, он вызываетfutex(FUTEX_WAKE)M, который сигнализирует ядруGо необходимости пробудить один или несколько потоков из очередиF, переводя их в состояние готовностиN.
Это гибридное взаимодействие является ключевым элементом, который обеспечивает futex его высокую производительность и гибкость.
Системный вызов futex() и внутренняя реализация в ядре
Интерфейс системного вызова futex(): Аргументы и операции
Системный вызов futex() является основным каналом взаимодействия между пользовательским пространством и ядром Linux для реализации механизмов futex. Он предоставляет программы метод для ожидания, пока определенное условие не станет истинным, или для пробуждения ожидающих потоков/процессов. Важно понимать, что программы пользовательского пространства обычно используют futex() только тогда, когда им, вероятно, придется блокироваться на длительное время, ожидая выполнения условия, избегая его в бескризисных сценариях.
Интерфейс системного вызова futex() отличается своей многофункциональностью и, как следствие, некоторой сложностью. Его сигнатура выглядит следующим образом:
long syscall(SYS_futex, uint32_t *uaddr, int futex_op, uint32_t val,
const struct timespec *timeout, /* or: uint32_t val2 */
uint32_t *uaddr2, uint32_t val3);
Давайте разберем основные аргументы:
uaddr: Это указатель наfutex word— 32-битное атомарное целое число в пользовательском пространстве, которое является объектом синхронизации.futex_op: Этот целочисленный аргумент определяет конкретную операцию, которую необходимо выполнить. Это ключевой параметр, который преобразует один системный вызовfutex()в множество различных примитивов. Наиболее распространенные операции включают:FUTEX_WAIT: Ожидает, пока значение по адресуuaddrне изменится. Если текущее значение*uaddrравноval, вызывающий поток переходит в состояние ожидания. В противном случае (если*uaddr != val), системный вызов немедленно возвращается с ошибкойEWOULDBLOCK.FUTEX_WAKE: Пробуждает доvalпотоков, ожидающих по адресуuaddr. Еслиvalравно 0, ни один поток не будет пробужден. Еслиvalотрицательное или очень большое, могут быть пробуждены все ожидающие потоки.FUTEX_CMP_REQUEUE: Это более сложная операция, которая позволяет атомарно перенаправить потоки, ожидающие на одномfutex, на другойfutex. Она используется для оптимизации, чтобы избежать «ложных пробуждений» и последующих повторных блокировок. Если*uaddrравноval,FUTEX_CMP_REQUEUEпробуждает доval2потоков поuaddr, а затем перемещает доval3оставшихся ожидающих потоков сuaddrнаuaddr2.
val: Значение, используемое дляfutex_op. Например, дляFUTEX_WAITэто ожидаемое значениеfutex word; дляFUTEX_WAKEэто количество потоков для пробуждения.timeout: Указатель на структуруstruct timespec, задающую максимальное время ожидания. ЕслиtimeoutравенNULL, ожидание может быть бесконечным.uaddr2: Второй адресfutex word, используемый в операциях, таких какFUTEX_CMP_REQUEUE.val3: Третье значение, используемое в некоторых операциях (например, количество потоков для перенаправления вFUTEX_CMP_REQUEUE).
Механизмы ядра, обеспечивающие эффективность и низкую задержку
Эффективность и низкая задержка futex в значительной степени зависят от его внутренней реализации в ядре Linux. Ключевые механизмы включают:
- Гибридный подход: Как уже упоминалось, большая часть работы выполняется в пользовательском пространстве. Ядро привлекается только в случае конфликта, когда требуется поставить поток в очередь ожидания или пробудить его. Это минимизирует дорогие переключения контекста и системные вызовы.
- Атомарные операции: Перед вызовом ядра, поток выполняет атомарные операции на
futex wordв пользовательском пространстве. Ядро, при получении системного вызова, также выполняет атомарные проверки. Например, ядро блокирует поток только в том случае, если значениеfutex wordпо адресуuaddrвсе еще соответствует ожидаемому значениюval, предоставленному вызывающим потоком. Это критически важно для предотвращения гонок и обеспечения корректности. - Очереди ожидания (Wait Queues): Ядро внутренне сопоставляет каждый
futex word(идентифицируемый его адресомuaddrи, возможно, другими параметрами) с одной или несколькими очередями ожидания. Когда поток вызываетFUTEX_WAIT, он добавляется в соответствующую очередь. Когда поток вызываетFUTEX_WAKE, ядро извлекает потоки из этой очереди и переводит их в состояние готовности к выполнению. - Хеширование: Для эффективного управления множеством
futex wordи ассоциированными с ними очередями ожидания, ядро использует хеш-таблицы. Адресuaddrfutex wordхешируется для быстрого поиска соответствующей очереди ожидания. Это позволяет быстро находить и управлять очередями даже при большом количестве одновременно используемыхfutex-ов. - Минимальные структуры данных: Внутренние структуры данных ядра для
futexспроектированы таким образом, чтобы быть максимально легковесными и не требовать больших накладных расходов на создание и управление.
Сочетание этих механизмов позволяет futex достигать высокой производительности, предоставляя при этом надежную синхронизацию. Почему бы не использовать эту мощь для оптимизации ваших системных приложений?
futex_waitv(): Детальный обзор нового системного вызова
futex_waitv() — это значимое расширение функциональности futex, интегрированное в ядро Linux 5.16 (ноябрь 2021 года) как часть инициативы futex2. Этот системный вызов предназначен для решения проблемы ожидания на нескольких futex-ах одновременно, что является общей потребностью в сложных многопоточных и распределенных системах. До его появления, для ожидания на нескольких futex-ах разработчикам приходилось реализовывать менее эффективные паттерны, такие как циклический опрос (polling) или использование нескольких потоков ожидания.
Сигнатура futex_waitv():
long syscall(SYS_futex_waitv, struct futex_waitv *list,
size_t num_futexes, int flags,
const struct timespec *timeout);
Основные аргументы futex_waitv():
list: Указатель на массив структурstruct futex_waitv. Каждая такая структура описывает одинfutex, на котором поток готов ждать.num_futexes: Количество элементов в массивеlist.flags: Дополнительные флаги, влияющие на поведение системного вызова (например,FUTEX_WAITV_ASYNCдля асинхронного ожидания, если будет реализовано).timeout: Указатель на структуруstruct timespec, задающую максимальное время ожидания.
Структура futex_waitv:
struct futex_waitv {
uint64_t uaddr; // Адрес futex word
uint64_t val; // Ожидаемое значение futex word
uint32_t flags; // Флаги для конкретного futex (например, FUTEX_32)
uint32_t __reserved; // Зарезервировано для выравнивания/будущего использования
};
Каждый элемент в массиве list содержит:
uaddr: Виртуальный адресfutex word, за которым нужно наблюдать.val: Ожидаемое значениеfutex word. Поток будет ждать, пока*uaddrне станет отличным отval.flags: Флаги, специфичные для конкретногоfutexв массиве (например,FUTEX_32явно указывает на 32-битныйfutex, хотя сейчас это единственный поддерживаемый размер).
Сценарии использования futex_waitv():
- Аналогия с
WaitForMultipleObjects:futex_waitv()позволяет эффективно реализовать функциональность, аналогичнуюWaitForMultipleObjectsв Windows, где поток может ожидать любого из нескольких событий. Это полезно для реализации сложных паттернов синхронизации, таких как ожидание на нескольких каналах связи, таймерах или условных переменных. - Сетевые приложения и высокопроизводительные серверы: В приложениях, обрабатывающих множество одновременных запросов,
futex_waitv()может помочь более эффективно управлять состоянием готовности различных асинхронных операций, ожидая завершения любой из них. - Распределенные системы: Для координации действий между компонентами распределенной системы, когда необходимо ожидать готовности нескольких ресурсов.
futex_waitv() предлагает более мощный и выразительный API для сложных сценариев ожидания, устраняя необходимость в громоздких обходных путях и улучшая общую эффективность синхронизации.
Преимущества, недостатки и оптимальные сценарии применения Futex
Futex — это мощный, но специализированный инструмент в арсенале системного программиста. Понимание его сильных и слабых сторон критически важно для принятия обоснованных решений при проектировании высокопроизводительных параллельных систем.
Преимущества: Производительность, гибкость, экономия ресурсов
- Высокая производительность в бескризисных сценариях: Это, пожалуй, главное преимущество
futex. Большая часть операций синхронизации (проверка состояния, попытка захвата) выполняется непосредственно в пользовательском пространстве с использованием атомарных инструкций процессора. Это полностью исключает накладные расходы на системные вызовы и переключения контекста, которые характерны для традиционных механизмов синхронизации. В условиях низкой конкуренцииfutexобеспечивает синхронизацию с задержкой в наносекунды. - Низкая задержка: Ядро задействуется только при возникновении реального конфликта, когда потоку действительно необходимо перейти в состояние ожидания. Это гарантирует, что даже в случае блокировки накладные расходы на переход в ядро и обратно минимизированы по сравнению с механизмами, которые всегда требуют системного вызова.
- Гибкость:
Futexявляется низкоуровневым строительным блоком. Это означает, что он не привязан к конкретной семантике (как, например, мьютекс), а предоставляет базовые операции ожидания и пробуждения. Такая гибкость позволяет реализовывать на его основе практически любые высокоуровневые примитивы синхронизации: мьютексы, семафоры, условные переменные, барьеры, блокировки чтения-записи и многое другое. Это делает его идеальной основой для системных библиотек. - Экономия ресурсов CPU: В отличие от спинлоков (которые активно «крутятся» в цикле, потребляя процессорные ресурсы, пока ресурс не освободится),
futexпозволяет ожидающему потоку перейти в спящий режим в ядре. Это освобождает процессор для выполнения других полезных задач, что особенно важно в системах с ограниченными ресурсами или высокой степенью многозадачности. - Поддержка приоритетов (PI futexes): Расширения
PI futexesпозволяют предотвращать инверсию приоритетов, что является критически важным для систем реального времени, где предсказуемость и строгое соблюдение приоритетов исполнения являются обязательными. - Robust futexes: Поддержка «robust futexes» значительно повышает надежность системы, обеспечивая корректную обработку ситуаций, когда поток-владелец блокировки аварийно завершается. Это предотвращает «зависание» ресурсов и обеспечивает устойчивость приложений.
Недостатки: Сложность использования, платформозависимость
- Сложность использования:
Futex— это очень низкоуровневый инструмент. Прямое его использование требует глубокого понимания атомарных операций, особенностей архитектуры процессора, нюансов работы ядра Linux и строгой семантики. Это делает его непригодным для большинства прикладных разработчиков, которые обычно предпочитают более высокоуровневые абстракции. Неправильное использованиеfutexможет привести к трудноуловимым ошибкам синхронизации, таким как гонки данных, взаимоблокировки и ложные пробуждения. Документ Ульриха Дреппера «Futexes Are Tricky» (2004) является важным предупреждением о сложности и потенциальных «подводных камнях» его использования. - Отсутствие гарантий пробуждения: Для некоторых операций (например,
FUTEX_WAKE) нет строгих гарантий, что будут пробуждены потоки с более высоким приоритетом (если не используютсяPI futexes) или что будет пробужден конкретный поток. Это требует от разработчика тщательно продумывать логику пробуждения. - Платформозависимость: Реализация
futexспецифична для ядра Linux. Хотя аналогичные механизмы существуют в других операционных системах (например,WaitOnAddressв Windows или mach semaphores в macOS/iOS), прямой перенос кода, использующегоfutex, на другие платформы невозможен без значительных переработок. - Отсутствие прямых оберток в glibc: Для прямого вызова
futex()часто требуется использовать универсальный системный вызовsyscall(2), посколькуglibcне предоставляет прямых оберток для всех его операций или предоставляет их в виде низкоуровневых функций, которые не являются частью стандартного POSIX API. Это еще больше усложняет его использование для обычных разработчиков.
Сценарии эффективности: От системных библиотек до высокопроизводительных приложений
Несмотря на сложность, futex является незаменимым инструментом в определенных сценариях:
- Реализация высокоуровневых библиотек синхронизации: Это основной и наиболее распространенный сценарий.
Futexидеально подходит в качестве фундамента для таких системных библиотек, какglibcи Native POSIX Thread Library (NPTL), которые предоставляют стандартизированные мьютексы (pthread_mutex_t), условные переменные (pthread_cond_t) и семафоры (sem_t). Благодаряfutex, эти высокоуровневые примитивы достигают высокой производительности в бескризисных сценариях, оставаясь при этом удобными и безопасными для прикладных разработчиков. - Высокопроизводительные многопоточные приложения: В приложениях, где синхронизация является критически важной для производительности, и где часто возникают бескризисные сценарии (потоки редко конфликтуют за один ресурс), прямое использование
futexили его специализированных оберток может обеспечить максимальную эффективность. Примерами могут служить высокочастотный трейдинг, низколатентные сетевые сервисы, обработка больших данных в реальном времени. - Низкоуровневое системное программирование: Разработка драйверов устройств, компонентов ядра, специализированных системных утилит или runtime-систем для языков программирования, требующих максимального контроля над механизмами синхронизации.
- Системы реального времени (Real-Time Systems): Благодаря поддержке
PI futexesи возможности тонкой настройки поведения синхронизации,futexявляется ключевым компонентом в системах, где предсказуемость задержек и строгое соблюдение приоритетов критически важны.
В целом, futex является основополагающим элементом производительной синхронизации в Linux, предлагая беспрецедентную скорость в бескризисных сценариях, но требующий глубоких знаний и осторожности при прямом использовании.
Futex в системных библиотеках: glibc и NPTL
Как уже неоднократно отмечалось, большинство прикладных разработчиков не взаимодействуют с futex напрямую. Вместо этого они используют высокоуровневые примитивы синхронизации, предоставляемые стандартными системными библиотеками. В экосистеме Linux центральное место занимает Native POSIX Thread Library (NPTL), которая является ключевой частью GNU C Library (glibc). Именно NPTL использует futex в качестве своей основной строительной платформы для реализации стандартных POSIX-потоковых примитивов.
Реализация pthread мьютексов и условных переменных
NPTL использует futex для реализации практически всех своих высокоуровневых примитивов синхронизации, включая pthread мьютексы, условные переменные и семафоры. Рассмотрим, как это происходит на примере pthread мьютекса:
- Структура
pthread_mutex_t: Внутри,pthread_mutex_t— это не просто абстрактный объект, а структура, которая содержит (или указывает на)futex word. Это 32-битное целое число, которое атомарно модифицируется потоками. Различные значения этого числа могут обозначать:- 0: Мьютекс свободен.
- 1: Мьютекс захвачен, но нет ожидающих потоков.
- 2: Мьютекс захвачен, и есть ожидающие потоки (или другие специфические состояния).
- Операция
pthread_mutex_lock():- Когда поток пытается захватить мьютекс, NPTL сначала выполняет атомарную операцию «сравнение-и-обмен» (CAS) на
futex wordмьютекса в пользовательском пространстве. Например, он пытается атомарно изменить значение с 0 на 1. - Бескризисный сценарий: Если CAS-операция успешна (т.е.
futex wordбыло 0, и поток смог атомарно установить его в 1), это означает, что мьютекс был свободен, и поток успешно его захватил. Все это происходит без системного вызова, значительно повышая производительность. - Конфликтный сценарий: Если CAS-операция не удалась (т.е.
futex wordуже было 1 или 2, что означает, что мьютекс занят), поток вынужден обратиться к ядру. NPTL вызывает системный вызовfutex()с операциейFUTEX_WAIT, передавая адресfutex wordи ожидаемое значение (например, 1). Ядро переводит поток в спящий режим и добавляет его в очередь ожидания, ассоциированную с этимfutex word.
- Когда поток пытается захватить мьютекс, NPTL сначала выполняет атомарную операцию «сравнение-и-обмен» (CAS) на
- Операция
pthread_mutex_unlock():- Когда поток освобождает мьютекс, он сначала атомарно изменяет
futex word(например, устанавливает его в 0). - Затем NPTL вызывает системный вызов
futex()с операциейFUTEX_WAKE, сообщая ядру, что необходимо пробудить один или несколько потоков, ожидающих на этомfutex word. Ядро выбирает потоки из очереди ожидания и переводит их в состояние готовности.
- Когда поток освобождает мьютекс, он сначала атомарно изменяет
Аналогичным образом futex используется для реализации условных переменных (pthread_cond_t). pthread_cond_wait() использует FUTEX_WAIT после освобождения мьютекса, а pthread_cond_signal() и pthread_cond_broadcast() используют FUTEX_WAKE для пробуждения одного или всех ожидающих потоков соответственно.
Примеры реализации высокоуровневых примитивов на основе Futex
Для иллюстрации, рассмотрим упрощенный псевдокод реализации мьютекса на основе futex:
// Глобальный или разделяемый между потоками futex_word
volatile int futex_word = 0; // 0: unlocked, 1: locked, >1: locked with waiters
void my_mutex_lock() {
int val = 0;
// 1. Попытка захвата в пользовательском пространстве (атомарно)
// __sync_val_compare_and_swap - GCC/Clang intrinsic для CAS
if (__sync_val_compare_and_swap(&futex_word, 0, 1) != 0) {
// Мьютекс уже занят
// Увеличиваем счетчик ожидающих потоков
if (__sync_fetch_and_add(&futex_word, 1) < 1) {
// Если futex_word был 0, но мы "промахнулись" с CAS,
// или если futex_word стал 1, и мы его увеличили,
// то теперь он >= 1. Если был 0, то стал 1.
// Если был 1, стал 2.
// Мы уже захватили блокировку или она была свободна.
// Нужно снова уменьшить счетчик и выйти.
__sync_fetch_and_sub(&futex_word, 1);
return; // Фактически захватили или он был свободен
}
// 2. Блокировка в ядре, если мьютекс занят
// Ожидаем, пока futex_word не станет 0
syscall(SYS_futex, &futex_word, FUTEX_WAIT, 2, NULL, NULL, 0);
}
}
void my_mutex_unlock() {
// 1. Попытка освободить мьютекс в пользовательском пространстве
// Если futex_word == 1 (захвачен, нет ожидающих), сбрасываем в 0
if (__sync_val_compare_and_swap(&futex_word, 1, 0) != 1) {
// Были ожидающие или futex_word был > 1
// Уменьшаем счетчик
__sync_fetch_and_sub(&futex_word, 1);
// 2. Пробуждаем один поток из ядра
syscall(SYS_futex, &futex_word, FUTEX_WAKE, 1, NULL, NULL, 0);
}
}
Пояснения к псевдокоду:
futex_wordиспользуется для отслеживания состояния мьютекса.0означает, что мьютекс свободен.1означает, что мьютекс захвачен, и нет ожидающих потоков. Значение больше1означает, что мьютекс захвачен, и есть ожидающие потоки.my_mutex_lock(): Сначала пытается атомарно изменитьfutex_wordс0на1. Если это удается, мьютекс захвачен без вызова ядра. Если нет, это означает конфликт. Поток увеличиваетfutex_word(помечая, что есть ожидающий) и затем вызываетFUTEX_WAIT, ожидая, покаfutex_wordне изменится. Аргумент2вFUTEX_WAITуказывает ядру, что поток ожидает, чтоfutex_wordбудет равно 2, прежде чем он заснет.my_mutex_unlock(): Сначала пытается атомарно изменитьfutex_wordс1на0. Если это удается, мьютекс освобожден без вызова ядра (нет ожидающих потоков). Если нет, это означает, что были ожидающие потоки (т.е.futex_wordбыло больше1). В этом случае поток уменьшаетfutex_wordи вызываетFUTEX_WAKEдля пробуждения одного ожидающего потока.
Этот упрощенный пример демонстрирует, как комбинация атомарных инструкций в пользовательском пространстве и системного вызова futex() обеспечивает эффективную реализацию мьютекса. Реальные реализации в glibc/NPTL, конечно, гораздо сложнее, они учитывают различные типы мьютексов (рекурсивные, ошибочные), robustness, priority inheritance, и множество других нюансов для максимальной производительности и корректности. Однако базовый принцип использования futex остается тем же.
Оптимизация и лучшие практики использования Futex
Хотя futex является мощным инструментом, его низкоуровневая природа требует особого внимания к деталям и соблюдения лучших практик для достижения максимальной производительности и надежности. Неправильное использование может привести к снижению производительности, гонкам данных, взаимоблокировкам и другим трудноуловимым ошибкам.
Минимизация конфликтов и атомарные операции в пользовательском пространстве
Основной принцип оптимизации futex заключается в минимизации ситуаций, когда ядру приходится вмешиваться. Каждый системный вызов futex() (особенно FUTEX_WAIT) влечет за собой переключение контекста, что является дорогостоящей операцией.
- Разработка бесконфликтных алгоритмов: В первую очередь, следует стремиться к проектированию алгоритмов, которые по своей природе минимизируют конкуренцию за общие ресурсы. Это может быть достигнуто за счет более гранулярных блокировок, использования lock-free структур данных, разделения данных по потокам или использования специализированных алгоритмов, таких как Read-Copy-Update (RCU).
- Активное использование атомарных инструкций: В тех случаях, когда блокировки неизбежны, необходимо максимально использовать атомарные инструкции процессора (такие как
compare-and-swap(CAS),fetch-and-add,exchange) в пользовательском пространстве для управленияfutex word. Эти инструкции позволяют потокам проверять и изменять состояниеfutex wordбез обращения к ядру, что обеспечивает почти мгновенную синхронизацию в бескризисных сценариях. - Правильное выравнивание
futex word:Futex wordвсегда должно быть 32-битным выровненным целым числом в общей памяти. Это важно для корректной работы атомарных операций процессора и для эффективного доступа к памяти.
Стратегии ожидания: Спиннинг перед блокировкой
В некоторых сценариях, особенно при очень коротких блокировках или ожидании освобождения ресурса, который, как ожидается, будет свободен очень скоро, может быть выгодно выполнить небольшое количество «спиннинга» (busy-waiting) в пользовательском пространстве перед тем, как вызвать FUTEX_WAIT.
- Идея спиннинга: Вместо немедленного перехода в ядро и блокировки, поток может короткое время активно «крутиться» в цикле, периодически проверяя состояние
futex word. Если блокировка освобождается в течение этого короткого интервала, поток может захватить ее без переключения контекста. - Когда это эффективно: Спиннинг оправдан, когда накладные расходы на системный вызов и переключение контекста превышают накладные расходы на короткое «кручение» CPU. Это обычно применимо к очень коротким критическим секциям или в условиях, когда поток-владелец блокировки, как ожидается, быстро ее освободит.
- Риски: Чрезмерный спиннинг может привести к неэффективному потреблению CPU, особенно в условиях высокой конкуренции или на одноядерных системах, где ожидающий поток будет занимать процессорное время, которое мог бы использовать поток-владелец. Поэтому количество итераций спиннинга должно быть тщательно подобрано. Многие высокоуровневые библиотеки синхронизации (например, NPTL) используют эвристики для определения оптимального времени спиннинга.
Продвинутые операции: FUTEX_CMP_REQUEUE и futex_waitv()
Ядро Linux предоставляет более сложные операции futex, которые могут быть использованы для дальнейшей оптимизации синхронизации:
FUTEX_CMP_REQUEUE: Эта операция позволяет атомарно перенаправлять ожидающие потоки с одногоfutexна другой. Это полезно в сценариях, где необходимо избежать «громоздких пробуждений» (thundering herd) или когда потоки, ожидающие на одном ресурсе, должны быть перенаправлены на другой, более доступный ресурс. Например, при освобождении мьютекса, вместо того чтобы пробуждать всех ожидающих и позволять им конкурировать за него, можно пробудить одного, а остальных перенаправить на другойfutex, чтобы избежать лишних переключений контекста.futex_waitv(): Как обсуждалось ранее, этот новый системный вызов (в Linux 5.16+) позволяет ожидать на массивеfutex-ов. Это значительно повышает эффективность в сценариях, требующих ожидания любого из нескольких событий, устраняя необходимость в сложных и менее эффективных обходных путях.
Использование этих продвинутых операций требует еще более глубокого понимания логики синхронизации и внутренней работы futex.
«Futexes Are Tricky» и соблюдение семантики
Одним из наиболее важных источников по нюансам использования futex является документ Ульриха Дреппера (Ulrich Drepper) «Futexes Are Tricky», опубликованный 27 июня 2004 года. Этот документ подчеркивает, что, несмотря на кажущуюся простоту, прямое использование futex сопряжено со значительными сложностями и потенциальными ловушками.
Неправильная последовательность атомарных операций и вызовов
futex()может привести к ситуациям, когда сигнал пробуждения отправляется до того, как поток успел перейти в состояние ожидания, или когда поток пробуждается без реальной необходимости.
- Гонки данных и «потерянные пробуждения»: Неправильная последовательность атомарных операций и вызовов
futex()может привести к ситуациям, когда сигнал пробуждения отправляется до того, как поток успел перейти в состояние ожидания, или когда поток пробуждается без реальной необходимости. - Взаимоблокировки: Ошибки в логике блокировки/разблокировки могут привести к классическим взаимоблокировкам, когда два или более потока ждут друг друга бесконечно.
- Ложные пробуждения:
futexможет пробудить поток даже без явного вызоваFUTEX_WAKE(например, из-за сигналов или системных событий). Поэтому, ожидающий поток всегда должен перепроверять условие, по которому он ждал, в цикле. - Строгая семантика: Каждая операция
futexимеет строгую семантику. Несоблюдение ожидаемых значенийfutex wordили неправильное использование флагов может привести к непредсказуемому поведению. - Необходимость атомарности: Все операции над
futex wordв пользовательском пространстве, которые влияют на его состояние, должны быть атомарными.
Важность изучения и понимания этих нюансов невозможно переоценить. Документ Дреппера остается актуальным руководством для всех, кто решает использовать futex напрямую.
Рекомендации по использованию: Когда стоит применять futex напрямую, а когда полагаться на библиотеки
Для большинства разработчиков прикладного программного обеспечения:
- ВСЕГДА полагайтесь на стандартные библиотеки:
glibcи NPTL предоставляют высокооптимизированные и хорошо протестированные реализацииpthreadмьютексов, условных переменных, семафоров и других примитивов, которые уже используютfutexпод капотом. Эти библиотеки берут на себя всю сложность низкоуровневого взаимодействия с ядром, предлагая безопасный и удобный API.
Когда стоит рассмотреть прямое использование futex (или его специализированных оберток):
- Разработка системных библиотек: Если вы создаете собственную библиотеку синхронизации или runtime-систему для языка программирования.
- Высокопроизводительные приложения с экстремальными требованиями к латентности: В таких областях, как высокочастотный трейдинг, где каждая наносекунда на счету, и где стандартные библиотеки могут вносить неприемлемые накладные расходы.
- Специализированные задачи, не покрываемые стандартными API: Например, реализация уникальных паттернов синхронизации или оптимизация для специфических аппаратных архитектур.
- Исследовательские цели: Для глубокого изучения и экспериментов с механизмами ядра.
В любом случае, прямое использование futex должно сопровождаться тщательным тестированием, профилированием и глубоким пониманием всех сопутствующих рисков.
Тенденции развития и перспективы Futex
Мир операционных систем и компьютерных архитектур постоянно эволюционирует, и механизмы синхронизации должны соответствовать этим изменениям. Futex, будучи ключевым компонентом ядра Linux, также не стоит на месте, претерпевая постоянные улучшения и расширения.
Дальнейшее развитие futex2 и упрощение API
Как уже упоминалось, futex2 является следующим шагом в эволюции futex, направленным на преодоление ограничений оригинального интерфейса. Уже интегрированный системный вызов futex_waitv() — это только начало. Дальнейшие перспективы futex2 включают:
- Поддержка разных размеров
futex-ов: В настоящее времяfutexработает только с 32-битными целыми числами.futex2призван обеспечить возможность использованияfutex-ов разных размеров (8, 16, 32 или 64 бита). Это может повысить гибкость и эффективность на различных архитектурах, позволяя использоватьfutexболее оптимально для меньших или больших структур данных синхронизации. - Флаг
FUTEX2_NUMA: Обсуждаются возможности добавления флагов, специфичных для NUMA-архитектур (Non-Uniform Memory Access). В многопроцессорных системах с NUMA, доступ к памяти, расположенной на «чужой» ноде, значительно медленнее, чем к локальной.FUTEX2_NUMAможет позволить ядру принимать более информированные решения о размещении очередей ожидания или пробуждении потоков, оптимизируя производительность в зависимости от топологии памяти. - Упрощение API: Исторически
futex()считается сложным системным вызовом из-за его многофункциональности и большого количества аргументов, которые перегружаются в зависимости от операции. В августе 2023 года Петер Зильстра (Peter Zijlstra), один из ключевых разработчиков ядра Linux, представил серию патчей, которые предлагают разделить существующийfutex()на несколько более сфокусированных и простых системных вызовов:futex_wait,futex_wakeиfutex_requeue. Эта инициатива направлена на уменьшение когнитивной нагрузки на разработчиков, делая API более модульным и менее подверженным ошибкам, при этом сохраняя всю функциональность.
Эти изменения демонстрируют стремление сообщества Linux не только расширять возможности futex, но и делать его более доступным и безопасным для использования.
Аналоги в других ОС и языках (C++20)
Хотя futex специфичен для Linux, концепция легковесной, гибридной синхронизации не уникальна. Другие операционные системы имеют свои аналогичные механизмы:
- Windows: В Windows существует API
WaitOnAddress, который предоставляет схожую функциональность. Потоки могут ожидать изменения значения по определенному адресу в памяти, а другие потоки могут сигнализировать об этом изменении. - Языки программирования: Идея эффективной синхронизации на основе атомарных операций и ожидания/пробуждения на ячейках памяти находит отражение и в стандартах языков программирования. Например, в C++20 были добавлены операции
atomic::wait,atomic::notify_oneиatomic::notify_all. Эти функции позволяют потокам атомарно ожидать изменения значенияatomicпеременной и быть пробужденными другими потоками. Хотя они не обязательно используютfutexнапрямую на всех платформах, их семантика очень схожа с базовыми операциямиfutex, предоставляя разработчикам высокоуровневый, платформонезависимый способ реализации эффективной синхронизации.
Это показывает, что концепция futex является фундаментальной и востребованной в современной разработке, и ее принципы находят воплощение в различных формах.
Масштабируемость и будущее синхронизации
По мере того как число ядер в процессорах продолжает расти, а архитектуры становятся все более сложными (например, гетерогенные системы, NUMA), требования к механизмам синхронизации становятся все более высокими.
- Улучшение Priority Inheritance (PI) и Robust futexes: Работа по совершенствованию этих механизмов продолжается. Цель — повысить предсказуемость и надежность синхронизации, особенно в системах реального времени и при работе с критически важными приложениями, где отказ одного компонента не должен приводить к краху всей системы.
- Масштабируемость: Разработчики ядра постоянно ищут и реализуют механизмы синхронизации, которые обеспечивают лучшую масштабируемость при высокой конкуренции. Это означает минимизацию глобальных блокировок, использование lock-free алгоритмов там, где это возможно, и оптимизацию путей кода, затрагивающих синхронизацию, чтобы они хорошо работали на системах с сотнями и тысячами ядер.
- Интеграция с новыми аппаратными возможностями: Будущее
futexтакже будет зависеть от новых аппаратных возможностей процессоров, таких как Transactional Memory (TM), которые могут предложить новые пути для оптимизации синхронизации.
Таким образом, futex и связанные с ним механизмы будут продолжать развиваться, адаптируясь к новым вызовам и потребностям высокопроизводительных вычислений, оставаясь при этом краеугольным камнем эффективной синхронизации в ядре Linux.
Заключение
Исчерпывающий анализ futex в ядре Linux раскрывает его как один из наиболее фундаментальных и, одновременно, сложных механизмов для реализации быстрой синхронизации в пользовательском пространстве. От его зарождения в стенах IBM и интеграции в ядро Linux в начале 2000-х годов, до текущих инициатив futex2 и системного вызова futex_waitv(), futex прошел путь постоянного развития, став краеугольным камнем для создания высокопроизводите��ьных многопоточных приложений.
Мы увидели, что гибридная природа futex — выполнение большинства операций в пользовательском пространстве при отсутствии конфликтов и привлечение ядра только в случае необходимости — обеспечивает беспрецедентную скорость и низкую задержку. Его способность выступать в роли низкоуровневого строительного блока позволила таким системным библиотекам, как glibc и NPTL, создавать надежные и эффективные pthread мьютексы, семафоры и условные переменные.
Вместе с тем, исследование выявило и «подводные камни» прямого использования futex. Его сложность, строгая семантика, необходимость глубокого понимания атомарных операций и особенностей ядра Linux делают его инструментом для опытных системных программистов. Однако, именно эти сложности открывают путь к созданию максимально оптимизированных решений в областях, где производительность и латентность имеют критическое значение.
Перспективы развития futex, включая дальнейшее совершенствование futex2, упрощение API и адаптацию к новым аппаратным архитектурам, подчеркивают его непреходящую значимость. В условиях роста числа ядер процессоров и увеличения требований к масштабируемости, futex будет продолжать играть центральную роль в обеспечении эффективной и надежной синхронизации в будущих версиях ядра Linux.
Таким образом, futex — это не просто системный вызов, а сложная, но элегантная инженерная концепция, которая лежит в основе современного параллельного программирования на Linux. Его изучение и понимание являются не просто академическим интересом, но и практической необходимостью для каждого, кто стремится создавать по-настоящему производительные и стабильные системные решения.
Список использованной литературы
- О.Бунин. Разработка высоконагруженных систем. 2011.
- Э.Таненбаум. Современные операционные системы. Питер, 2010.
- futex(7) — Arch manual pages. URL: https://mirrors.kernel.org/pub/linux/kernel/people/ (дата обращения: 13.10.2025).
- futex(2) — Linux manual page — man7.org. URL: https://man7.org/linux/man-pages/man2/futex.2.html (дата обращения: 13.10.2025).
- futex(7) — Linux manual page — man7.org. URL: https://man7.org/linux/man-pages/man7/futex.7.html (дата обращения: 13.10.2025).
- MAN futex (4) Специальные файлы /dev/* (FreeBSD и Linux) — Opennet.ru. URL: https://www.opennet.ru/man.shtml?topic=futex&category=4 (дата обращения: 13.10.2025).
- futex2 — The Linux Kernel documentation. URL: https://docs.kernel.org/userspace-api/futex2.html (дата обращения: 13.10.2025).
- A new futex API — LWN.net. URL: https://lwn.net/Articles/940602/ (дата обращения: 13.10.2025).
- Detailed Approach of the futex — R. Koucha. URL: https://rkoucha.developpez.com/futex/ (дата обращения: 13.10.2025).
- A futex overview and update — LWN.net. URL: https://lwn.net/Articles/360699/ (дата обращения: 13.10.2025).
- MAN futex (2) Системные вызовы (FreeBSD и Linux) — Opennet.ru. URL: https://www.opennet.ru/man.shtml?topic=futex&category=2 (дата обращения: 13.10.2025).
- futex — быстрая блокировка в пользовательском пространстве — Ubuntu Manpage. URL: https://manpages.ubuntu.com/manpages/bionic/man2/futex.2.html (дата обращения: 13.10.2025).