__atomic_futex_unsigned
Что есть фьютекс? Во-первых, это примитив с ужасным именем. Во-вторых, сложно придумать название хуже. Разве что придумать новый примитив "хьютекс".
К счастью, до такого пока никто не опустился, а фьютекс - это самый настоящий системный механизм Linux. Как учит нас педивикия, это просто fast userspace mutex, быстрый мьютекс пространства пользователя. В общем, это вроде как мьютексы, только не достают ядро своими запросами. Фьютекс, лежащий в пространстве пользователя, будет мгновенно захвачен потоком, если ресурс свободен, без всяких обращений к ядру. К нему придется обратиться (вызов futex()), только когда ресурс уже занят другим потоком и нужно перевести текущий поток в режим ожидания.
Ничего удивительного, что нечто такое появилось и в GCC. И вот мы видим, в std::future член _M_status имеет тип __atomic_futex_unsigned<>.
Название намекает, что тип этот не для простых разработчиков, а хитровыделанных библиотекоделов.
Этот тип доступен только если определен _GLIBCXX_HAS_GTHREADS, что логично - не зря мы переопределили его вручную, столько открылось интересных возможностей.
Если же выполняется условие defined(_GLIBCXX_HAVE_LINUX_FUTEX) && ATOMIC_INT_LOCK_FREE > 1, то есть в нашем "линуксе" доступны фьютексы, и операции с типом std::atomic<int> всегда будут lock-free, то примитив синхронизации можно сложить из atomic<unsigned> и прямых вызовов futex.
Правда, никакие вызовы futex у нас не реализованы, как и не определен макрос _GLIBCXX_HAVE_LINUX_FUTEX.
Мы готовы потратить кучу времени на реализацию вызовов futex во FreeRTOS? Конечно нет, у нас есть личная жизнь.
А у реализации __atomic_futex_unsigned есть другая ветка, где фьютекс скомпонован из других примитивов:
𝚝𝚎𝚖𝚙𝚕𝚊𝚝𝚎 <𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 _𝚆𝚊𝚒𝚝𝚎𝚛_𝚋𝚒𝚝 = 𝟶𝚡𝟾𝟶𝟶𝟶𝟶𝟶𝟶𝟶>
𝚌𝚕𝚊𝚜𝚜 __𝚊𝚝𝚘𝚖𝚒𝚌_𝚏𝚞𝚝𝚎𝚡_𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 {
...
𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 _𝙼_𝚍𝚊𝚝𝚊;
𝚖𝚞𝚝𝚎𝚡 _𝙼_𝚖𝚞𝚝𝚎𝚡;
𝚌𝚘𝚗𝚍𝚒𝚝𝚒𝚘𝚗_𝚟𝚊𝚛𝚒𝚊𝚋𝚕𝚎 _𝙼_𝚌𝚘𝚗𝚍𝚟𝚊𝚛;
Само значение фьютекса хранится в _M_data (__not_ready или __ready). Для синхронизации понадобилось аж два примитива: мьютекс и условная переменная.
Например, для получения значений используется мьютекс.
𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 _𝙼_𝚕𝚘𝚊𝚍(𝚖𝚎𝚖𝚘𝚛𝚢_𝚘𝚛𝚍𝚎𝚛 __𝚖𝚘) {
𝚞𝚗𝚒𝚚𝚞𝚎_𝚕𝚘𝚌𝚔<𝚖𝚞𝚝𝚎𝚡> __𝚕𝚘𝚌𝚔(_𝙼_𝚖𝚞𝚝𝚎𝚡);
𝚛𝚎𝚝𝚞𝚛𝚗 _𝙼_𝚍𝚊𝚝𝚊;
}
А условная переменная нужна, чтоб усыпить поток
𝚟𝚘𝚒𝚍 _𝙼_𝚕𝚘𝚊𝚍_𝚠𝚑𝚎𝚗_𝚎𝚚𝚞𝚊𝚕(𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 __𝚟𝚊𝚕, 𝚖𝚎𝚖𝚘𝚛𝚢_𝚘𝚛𝚍𝚎𝚛 __𝚖𝚘) {
𝚞𝚗𝚒𝚚𝚞𝚎_𝚕𝚘𝚌𝚔<𝚖𝚞𝚝𝚎𝚡> __𝚕𝚘𝚌𝚔(_𝙼_𝚖𝚞𝚝𝚎𝚡);
𝚠𝚑𝚒𝚕𝚎 (_𝙼_𝚍𝚊𝚝𝚊 != __𝚟𝚊𝚕)
_𝙼_𝚌𝚘𝚗𝚍𝚟𝚊𝚛.𝚠𝚊𝚒𝚝(__𝚕𝚘𝚌𝚔);
}
_M_load_when_equal ничего не возвращает, просто блокирует текущий поток, пока значение _M_data не будет равно переданному значению __val. И все это под прикрытием мьютекса.
Изменения состояния должны происходить через функцию
𝚟𝚘𝚒𝚍 _𝙼_𝚜𝚝𝚘𝚛𝚎_𝚗𝚘𝚝𝚒𝚏𝚢_𝚊𝚕𝚕(𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 __𝚟𝚊𝚕, 𝚖𝚎𝚖𝚘𝚛𝚢_𝚘𝚛𝚍𝚎𝚛 __𝚖𝚘) {
𝚞𝚗𝚒𝚚𝚞𝚎_𝚕𝚘𝚌𝚔<𝚖𝚞𝚝𝚎𝚡> __𝚕𝚘𝚌𝚔(_𝙼_𝚖𝚞𝚝𝚎𝚡);
_𝙼_𝚍𝚊𝚝𝚊 = __𝚟𝚊𝚕;
_𝙼_𝚌𝚘𝚗𝚍𝚟𝚊𝚛.𝚗𝚘𝚝𝚒𝚏𝚢_𝚊𝚕𝚕();
}
Опять же удачно, что и условная переменная, и мьютекс у нас реализованы. Это значит, можно перейти к натурным испытаниям!