Основная идея статьи - сравнить производительность std::conditional_variable и std::atomic_flag::wait из С++20, посмотреть примеры использования.  Когда встает вопрос об ожидании какого-то события/событий, то одно из первых что приходит на ум - это std::conditional_variable. Согласно cppreference: The condition_variable class is a synchronization primitive used with a std::mutex to block one or more threads until another thread both modifies a shared variable (the condition) and notifies the condition_variable. т.е. condition_variable - это примитив синхронизации, который работает поверх std::mutex (если быть точным - то используется напрямую std::unique_lock, который уже работает поверхstd::mutex). Давайте быстро посмотрим примеры и кратко разберем как это работает. Вывод будет следующим: Все просто: создаются 2 потока, один пишет, другой считывает новое сообщение.  Начнем с produce(). и первое, что увидим - это std::lock_guard. На (5) строке мы захватываем mutex c помощью std::lock_guard. Это пример RAII. Мы получаем по ссылке шаблон, у которого есть методы lock и unlock и сохраняем внутри non-const-ссылку. И вызываем lock/unlock в ctor/dtor. Копирование помечено как delete. Все просто.  Есть интересный момент, а именно конструктор: Это конструктор не блокирует переданный mutex, но при этом в деструкторе все также вызывается unlock. Так в чем же дело? adopt_lock_t можно считать некоторым тегом, который сообщает lock_guard, что переданный mutex уже заблокирован, но в деструкторе его также надо освобождать. Все становится еще более ясным, когда переведем с английского adopt (принять, принимать). adopt_lock - принимает блокировку.  Для чего нужен adopt_lock? До С++17 и появления std::scoped_lock это был способ для блокирования нескольких mutex и  правильной их разблокировки + exception safe. lock_guard разобрали, давайте смотреть дальше produce. Под заблокированным mutex изменяем данные.  текущая область видимости ограничена  (4) и (7) строчками.  Нужно ли под mutex вносить notify_one - разберем дальше. сам notify_one() разберем дальше вместе с wait. Переходим к consume() и здесь первое, что мы видим - это std::unique_lock. На (12) строке мы захватываем mutex с помощью std::unique_lock. И это тоже RAII.  Внутри этот лок сохраняет сырой указатель на mutex. Появляется дополнительное поле _M_owns - по которому определяется, кто владелец этой блокировки. Разберем пример работы конструктора: Сохраняет адрес переданного mutex в _M_device и вызываем lock(), где указываем, что мы владелец блокировки. Странно конечно, что _M_owns = true здесь будет вызвано 2 раза. Ну да ладно. Посмотрим деструктор: unlock() будем звать только если мы владелец блокировки, в отличии от деструктора std::lock_guard, где в деструкторе всегда вызывается unlock(). Есть знакомый нам конструктор со знакомым тегом adopt_lock: Принимаем заблокированный mutex и становимся его владельцем. Но есть и новые теги. Например defer_lock: С англ. defer - отложить т.е. в данном контексте отложить блокировку.  Но кто тогда будет разблокировать mutex, если в деструкторе как мы видели unlock() вызывается только если мы владелец блокировки, а в этом конструкторе _M_owns(false)? std::unique_lock предоставляет гораздо больше функциональности нежели std::lock_guard. Например метод lock()/unlock(). И при создании unique_lock с  defer_lock можно впоследствии этот unique_lock передать в std::lock, который вызовет lock() и сделает этот unique_lock владельцем блокировки. Дополнительно есть try_to_lock_t, duration логика, но на них останавливаться не буду. Где это использовать? Например тот же пример, что и у lock_guard - блокировка нескольких mutex до появления std::scoped_lock. Возвращаемся к методу consume() и смотрим следующую строчку (13). До начала выполнения wait мы имеем заблокированный mutex - владелец блокировки std::unique_lock. В wait мы передаем заблокированный std::unique_lock, wait снимает временно блокировку и усыпает (не всегда: когда предикат в  wait истинный может и не уснуть). Через какое-то время поток просыпается и вызывает блокировку. Именно из-за того, что нужна функциональность lock/unlock здесь нельзя использовать std::lock_guard - у него попросту нет такой функциональности. Отдельно стоит поговорить про пробуждения потока на wait. Варианты пробуждения потока: notify_one()/notify_all(): Вызываем из другого потока и wait проверяет предикат. Интересный момент с удержанием блокировки при notify_*. Для всех mutex рекомендуется держать блокировку не дольше чем нужно. В моем примере это до (6) строчки включительно. Но как быть с notify? Ответ как всегда в документации:  The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s); in fact doing so is a pessimization, since the notified thread would immediately block again, waiting for the notifying thread to release the lock. However, some implementations (in particular many implementations of pthreads) recognize this situation and avoid this "hurry up and wait" scenario by transferring the waiting thread from the condition variable's queue directly to the queue of the mutex within the notify call, without waking it up. Notifying while under the lock may nevertheless be necessary when precise scheduling of events is required, e.g. if the waiting thread would exit the program if the condition is satisfied, causing destruction of the notifying thread's condition variable. A spurious wakeup after mutex unlock but before notify would result in notify called on a destroyed object. spurious wakeup: они же ложные пробуждения. Они есть, но мнения причины их существования/возникновения встречал разные. Есть такое объяснение - когда поток просыпается, сначала он должен взять блокировку - вызвав syscall. Между пробуждением и вызовом syscall проходит время - какой-то интервал времени. За этот интервал времени состояние системы может измениться (например условие которое нас пробудило перестало быть истинным). И в итоге получаем ложный вызов. Поэтому после wait рекомендуют всегда проверять истинность условие пробуждения. Есть интересное старое обсуждение.  существуют std::conditional_variable_any, wait_for ... - тут рассматривать не буду. std::atomic_flag - это самый простой атомарный тип, представляющий собой булев флаг. Объекты этого типа  могут находятся в одном из 2-х состояний: установлен или сброшен. В С++20 у него появились новые методы: wait(), notify_*(). И кажется, что в самых простых сценариях - простое изменение флага, это может быть альтернативой для std::conditional_variable. Набросаю небольшой пример игры пинг-понга (полное исходники: репозиторий): Версия с conditional_variable: Версия с atomic_flag: Для замера времени: Результаты замеров: Локальный замер: ОС: ubuntu 22.04, CPU: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz RAM: 32 Гб  clang-14 release gcc-11 release версия с conditional_variable 4.0581 - 4.29326 - 4.15922 seconds 4.15656 - 3.96363 - 3.94372 seconds версия с atomic_flag 0.250251 - 0.24626 - 0.23954 seconds 0.263211- 3.96363 - 0.260344 seconds Замер в CI: можно посмотреть здесь Также сделал замеры на виртуальной машине в cloud: результаты описал в readme ссылка Новый wait выглядит очень хорошей альтернативой для std::conditional_variable, когда у нас есть простой предикат - бонусом будет чуть более шустрая работа.  Software Engineer