Код: Выделить всё
#include
#include
#include
extern void block_wait();
extern void wake();
int main(){
std::atomic counter = 0;
std::jthread t1([&](){
if(counter.fetch_sub(1,std::memory_order::relaxed) == 0){ // #0
block_wait(); // #1
}
});
std::jthread t2([&](){
if(counter.fetch_add(1,std::memory_order::relaxed) == -1){ // #2
wake(); // #3
}
});
}
Если #0 читает 0, #2 гарантированно прочитает -1 и выполнит пробуждение() для пробуждения #1. Однако если мы изменим #2 на чистую загрузку следующим образом:
Код: Выделить всё
#include
#include
#include
extern void block_wait();
extern void wake();
int main(){
std::atomic counter = 0;
std::jthread t1([&](){
if(counter.fetch_sub(1,std::memory_order::relaxed) == 0){ // #0
block_wait(); // #1
}
});
std::jthread t2([&](){
if(counter.load(std::memory_order::relaxed) == -1){ // #2
wake(); // #3
}
});
}
Какова правильная причина, почему #2 в качестве операции RMW не пропускает выполнение Wake() по сравнению с чистой загрузкой с точки зрения стандартного/абстрактного машинного смысла C++? Я пытаюсь дать три объяснения; если объяснение неверно, укажите, почему.
- Загрузочная часть операции RMW менее склонна к считыванию устаревшего значения, чем чистая загрузка.
- Часть загрузки операции RMW более склонна к чтению более поздней модификации в порядке модификации, чем чистая загрузка.
- Стандарт C++ накладывает более строгие ограничения на загрузочную часть операции RMW, чем на чистую загрузку.
Мобильная версия