Мы точно знаем, что functionThatWillBeLaunchedInThreadAfunction
будет запущен ПОСЛЕ окончания functionThatWillBeLaunchedInThreadB
.
Прежде всего, если это так, то, вероятно, вашМеханизм очереди задач уже обеспечивает необходимую синхронизацию.
В ответ на ответ ...
На данный момент самая простая вещь, которую нужно сделать, это упорядочить получение / выпуск.Все решения, которые вы дали, хуже.
std::atomic_bool canBegin{false};
void functionThatWillBeLaunchedInThreadA() {
if(canBegin.load(std::memory_order_acquire))
produceData();
}
void functionThatWillBeLaunchedInThreadB() {
canBegin.store(true, std::memory_order_release);
}
Кстати, разве это не должно быть циклом while?
void functionThatWillBeLaunchedInThreadA() {
while (!canBegin.load(std::memory_order_acquire))
{ }
produceData();
}
Мне не нужно защищатьданные, поэтому порядок получения / потребления / выпуска для атомарного хранилища / загрузки здесь не нужен (я думаю?)
В этом случае порядок необходим для того, чтобы подсистема компилятора / ЦП / памяти незаказ магазина canBegin
true
до завершения предыдущих операций чтения / записи.И он должен фактически останавливать ЦП, пока не будет гарантировано, что каждая запись, предшествующая порядку программы, будет распространяться до сохранения до canBegin
.На стороне загрузки это предотвращает чтение / запись памяти до того, как canBegin
будет прочитано как true
.Если я правильно помню, это переупорядочение на самом деле не произойдет на любом современном оборудовании в этом конкретном случае из-за зависимости данных.(Я не уверен, как это играет с умозрительным исполнением).Расслабленный порядок в памяти не дает ни одной из этих гарантий упорядочения.
Однако в таком коде у нас нет никакой гарантии, что хранилище будет видно в потоке A, поэтому поток A может прочитатьустаревшее значение (false).
Вы сами сказали:
хранилище атомарной переменной должно быть видно для другого потока в "в течение разумного промежутка времени".
Даже при неупорядоченном порядке памяти запись гарантированно в конечном итоге достигнет других ядер, и все ядра в конечном итоге согласятся с историей хранения любой заданной переменной, поэтому устаревшие значения отсутствуют.Есть только значения, которые еще не распространились.Что «расслаблено» в этом, так это порядок хранения по отношению к другим переменным.Таким образом, memory_order_relaxed
решает проблему устаревшего чтения (но не учитывает порядок, требуемый, как описано выше).
Не используйте volatile
.Он не обеспечивает всех гарантий, требуемых для атомарности в модели памяти C ++, поэтому его использование будет неопределенным поведением.См. https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering внизу, чтобы прочитать об этом.
Вы можете использовать мьютекс или спин-блокировку.Атомная операция намного дороже, чем приобретение / выпуск.Спинлок будет выполнять хотя бы одну атомарную операцию чтения-изменения-записи ... и, возможно, многие.Мьютекс определенно излишний.Но оба имеют преимущество простоты.Большинство людей знают, как использовать блокировки, чтобы легче было продемонстрировать правильность.
Ограничение памяти также будет работать, но ваши ограждения находятся в неправильном месте (это противоречит интуиции), и переменная связи между потоками должна быть std::atomic
.(Осторожно, играя в эти игры ...! Поведение с неопределенным поведением легко определить) Благодаря оградам можно спокойно упорядочивать.
std::atomic<bool> canGo{false}; // MUST be atomic
// in thread A
if(canGo.load(std::memory_order_relaxed))
{
std::atomic_thread_fence(std::memory_order_acquire);
produceData();
}
// in thread B
std::atomic_thread_fence(std::memory_order_release);
canGo.store(true, memory_order_relaxed);
Заборы памяти на самом деле более строгие, чем порядок получения / выпуска на std::atomic
load / store, так что это ничего не дает и может стоить дороже.
Похоже, вы действительно хотите избежать накладных расходов с помощью вашего механизма сигнализации.Это как раз то, для чего была изобретена семантика получения / выпуска std::atomic
!Вы слишком беспокоитесь о устаревших ценностях.Да, атомарный RMW даст вам «последнюю» ценность, но они также являются очень дорогими операциями.Я хочу дать вам представление о том, как быстро происходит приобретение / выпуск.Скорее всего, вы ориентируетесь на x86.В x86 общий порядок хранения, а загрузки / хранилища размером в слово являются атомарными, поэтому сборка загрузки компилируется только в обычную загрузку, а хранилище релизов компилируется в обычное хранилище.Так что получается, что почти все в этом длинном посте, вероятно, все равно будет компилироваться с точно таким же кодом.