Упорядочение неатомарных операций за счет использования атомарных операций в качестве основы для средств синхронизации более высокого уровня

Сначала я привожу описание Энтони Уильямса "Параллельность в C++":

class spinlock_mutex

{

 std::atomic_flag flag;

public:

 spinlock_mutex():

     flag(ATOMIC_FLAG_INIT)
 {}

 void lock()

 {

     while(flag.test_and_set(std::memory_order_acquire));
 }

 void unlock()

 {

     flag.clear(std::memory_order_release);
 }

};

Операция lock () представляет собой цикл в flag.test_and_set() с использованием std::memory_ order_acquire, а unlock () - это вызов flag.clear() с std:: memory_order_release. Когда первый поток вызывает lock(), флаг изначально очищается, поэтому первый вызов test_and_set () установит флаг и вернет false, указывая, что этот поток теперь имеет блокировку, и заканчивая цикл. Затем поток может свободно изменять любые данные, защищенные мьютексом. Любой другой поток, который вызывает lock () в это время, найдет уже установленный флаг и будет заблокирован в цикле test_and_set ().

Когда поток с блокировкой завершил изменение защищенных данных, он вызывает unlock(), который вызывает flag.clear() с семантикой std:: memory_order_release. Затем он синхронизируется с (см. Раздел 5.3.1) последующим вызовом flag.test_and_set() из вызова lock () в другом потоке, потому что этот вызов имеет семантику std:: memory_order_acquire. Поскольку модификация защищенных данных обязательно секвенируется перед вызовом unlock(), эта модификация происходит перед unlock () и, таким образом, происходит перед последующим вызовом lock () из второго потока (из-за соотношения между синхронизацией и разблокировкой). () и блокировки ()) и происходит до того, как любой доступ к этим данным из этого второго потока, как только он получил блокировку.

Q: Если есть только два потока, и поток A имеет объект m1 Запускает lock() впервые, и поток B имеет объект m1 Запускает lock() впервые перед m1 ссылающееся unlock() в теме, почему flag.test_and_set(std::memory_order_acquire) получить истинное, а не ложное (начальное значение), когда m1 Запускает lock функция в потоке B?

Я знаю последовательность выпуска, но для создания последовательности выпуска требуется атомарный объект, вызывающий атомарную операцию с std::memory_order_release и нет операции, вызванной с std::memory_order_release,

2 ответа

acquire а также release семантика относится к другому (защищенному) ресурсу, здесь не показан. В частности, не перемещайте доступ после блокировки или до разблокировки. Сами атомные операции полностью упорядочены.

Поскольку операции полностью упорядочены, ваш гипотетический порядок A:lock, B:lock, A:unlock рассматривается в одном и том же порядке обеими нитями. Следовательно, когда поток B вызывает lockВидит только lock от А, а не unlock,

Нить, делающая вещи друг перед другом, на самом деле не имеет смысла, кроме поведения, которое вы хотели бы знать. Memory_order не входит в это. Он определяет порядок упорядочения неатомарного доступа к памяти вокруг атомарной операции.

Причины для этого в том, что если вы делаете:

lock();
foo();
unlock();

В двух потоках foo в одном потоке не может прочитать или сразу перед блокировкой или после разблокировки рассматриваемого потока. Это в сочетании с атомарностью самих замков и разблокировок дает ожидаемое нами поведение. (т.е. нет одновременного доступа из foo).

Здесь только один std::atomic_flag, В любой момент это либо установлено (true) или очистить (false).

std::atomic_flag::test_and_set определяется как

Атомно меняет состояние std::atomic_flag установить (true) и возвращает значение, которое оно удерживало ранее.

Когда А позвонил lock, он изменил установленный флаг, поэтому состояние, возвращаемое, когда B пытается заблокировать, установлено. Это оценивается как условие while так что цикл продолжается. Поток B будет продолжать вращаться в этом цикле, пока не будет снята блокировка.

Наконец, когда A вызывает разблокировку, флаг меняется на очистить. Затем B может проверить снова, и false заканчивает цикл

Другие вопросы по тегам