Код: Выделить всё
#include
#include
#include
#include
int main(){
std::atomic val = 1;
std::atomic ptr = nullptr;
auto t1 = std::thread([&](){
auto v = val.load(std::memory_order::relaxed);
while(true){
if(v==-1){
v = val.load(std::memory_order::relaxed);
continue;
}
if(val.compare_exchange_strong(v,v+1,std::memory_order::acquire,std::memory_order::relaxed)==true){
break;
}
}
ptr.store(&val,std::memory_order::relaxed); // #1
});
auto t2 = std::thread([&ptr](){
std::atomic* val_ptr = ptr.load(std::memory_order::relaxed); // #2
while(val_ptr==nullptr){
val_ptr = ptr.load(std::memory_order::relaxed); // #3
}
int v = 1;
bool r = val_ptr->compare_exchange_strong(v,-1,std::memory_order::acquire,std::memory_order::relaxed); // #4
/*
if(r){
val_ptr->store(1,std::memory_order::release);
} // #6
*/
assert(r==false); // #5
});
t1.join();
t2.join();
}
Код: Выделить всё
#3Утверждение не выполняется, только если #4 возвращает true. Это означает, что #4 выполнена успешно, то есть операция RMW загружает 1 и записывает -1. Модификация, значение которой равно 1, является только начальным значением атомарного объекта val.
В этой программе все операции над атомарным объектом val являются либо операциями RMW, либо чистыми загрузками (т. е. сбоями CAS). Таким образом, все эти операции сериализуются в соответствии с [atomics.order] p10:
Атомарные операции чтения-изменения-записи всегда должны считывать последнее значение (в порядке модификации), записанное перед записью, связанной с операцией чтения-изменения-записи.
То есть никакие две операции RMW не могут прочитать одну и ту же модификацию. Если #4 считывает начальное значение, то операции CAS не должны завершиться успешно, и это просто чистая загрузка.
Не уверен, что следующий анализ является формальным:
- CAS в t1 не будет достигнут, если v равен -1, что приводит к тому, что цикл не завершается.
- Ошибки CAS приводят к тому, что цикл не завершается, что приводит к его завершению. вернемся к приведенному выше условию.
- Цикл завершится только в том случае, если CAS загрузит 1 и запишет 2 в этой программе.
Цикл в t2 не может выйти, если загрузка читает nullptr. Таким образом, #5 может быть достигнут только в том случае, если цикл в t2 существует, что требует наличия некоторого побочного эффекта, отличного от нуля. В этой программе побочный эффект создания ненулевого указателя может быть только #1. Но согласно приведенному выше предположению #1 никогда не достигается. Следовательно, этот побочный эффект не может существовать где-либо в течение жизни программы.
Таким образом, #2 и #3 не могут прочитать значение, записанное в #1, иначе это нарушит [intro.races] p10:
Значение атомарного объекта M, определенное оценкой B, представляет собой значение, сохраненное некоторым неуказанным побочным эффектом A, который изменяет M, где B не происходит раньше О.
Итак, утверждение никогда не может потерпеть неудачу. Верен ли мой анализ?
Обновление:
Как будет вести себя утверждение при введении прокомментированного #6? IICU, если #5 завершается неудачно, цикл в t1 может завершиться только тогда, когда CAS прочитает значение, записанное #6; однако при этом выполнении #6 будет синхронизироваться с CAS в момент t1, что означает, что #2 не сможет прочитать #1(
Код: Выделить всё
#2Если мы изменим порядок памяти в #6 на расслабленный, похоже, что утверждение может потерпеть неудачу? Кажется, OOTA, но технически это может произойти.
Подробнее здесь: https://stackoverflow.com/questions/798 ... to-transfe
Мобильная версия