Привет. Сперва код, затем пояснение и вопрос:
#include <cassert>
#include <thread>
#include <atomic>
#include <vector>
#include <iostream>
using namespace std;
enum mtx_state {
e_free,
e_busy,
e_block
};
std::atomic<mtx_state> mtx = e_free;
std::atomic_int cnt = 0;
std::atomic_flag barrier;
int result;
void t0() {
while (! barrier.test(memory_order_relaxed));
mtx_state expected = e_free;
while (! atomic_compare_exchange_strong_explicit(&mtx, &expected, e_busy,
memory_order_relaxed, memory_order_relaxed))
if (expected == e_block)
return;
else
expected = e_free;
cnt.fetch_add(1, memory_order_relaxed);
expected = e_busy;
if (! atomic_compare_exchange_strong_explicit(&mtx, &expected, e_free,
memory_order_relaxed, memory_order_relaxed))
terminate();
}
void t1() {
while (! barrier.test(memory_order_relaxed));
mtx_state expected = e_free;
while (! atomic_compare_exchange_strong_explicit(&mtx, &expected, e_block,
memory_order_relaxed, memory_order_relaxed))
expected = e_free;
result = cnt.load(memory_order_relaxed);
}
int main() {
if (true) {
vector<jthread> th;
for (int i = 0; i != 30; ++i)
th.emplace_back(t0);
th.emplace_back(t1);
barrier.test_and_set();
}
assert(cnt.load(memory_order_relaxed) == result);
cout << result << endl;
}
Идея примера - стартует 30 потоков, на входе долбятся о барьер для одновременного запуска. Задача каждого из них - взять «мьютекс» (атомик с несколькими состояниями - {свободно, занято, блокировка}, если блокировка, то поток завершается без выполнения работы) и инкрементировать счетчик. Также вместе с этими 30 стартует 1, который блокирует «мьютекс» и читает значение из счетчика в итоговый result.
Дело всё вот в чем - взятие и освобождение мьютекса - relaxed операция. Значит когда блокирующий поток финально захватывает мьютекс, то он как бы не обязан увидеть все изменения, которые возглавлял последний release мьютекса, очевидно, что среди них находится инкремент счетчика.
Если счетчик не атомик, то вопроса бы не было, но он атомик. Возможна ли ситуация, когда последнее (блокирующее) взятие мьютекса увидит непоследнее состояние счетчика? Т.е. блокирующий поток читает счетчик, 10 например, кладет его в result, блокирует мьютекс, дальше каким-то чудом счетчик инкрементится, срабатывает assert, это возможно?