为什么使用原子 CAS 的程序不能保持线程安全?

wod*_*der 2 c++ thread-safety c++11 stdatomic


int main(){

    atomic<bool> atomic_lock(false);
    std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
    int count = 0;


    auto f = [&](){
        bool flag = false;
        
        for( int i = 0; i< 10000000; ++i){
          while(!atomic_lock.compare_exchange_strong(flag, true)){}
          //while(lock_flag.test_and_set(std::memory_order_seq_cst));

          ++count;
          //lock_flag.clear(std::memory_order_seq_cst);
          atomic_lock.store(false, std::memory_order_seq_cst);
        }
    };

    thread t1(f);
    thread t2(f);
    t1.join();
    t2.join();

    cout<<count<<endl;

    return 0;
}

Run Code Online (Sandbox Code Playgroud)

这是我的程序,我想用 CAS 替换互斥锁,但不是 20000000 的输出表明它不是线程安全程序,哪里错了?但是,我用 atomic_flag 替换 atomic 显示如上,输出是正确的

Pet*_*des 5

您忘记flag = false在每次 CAS 尝试之前进行设置,以确保您仅在 CAS 从 false 变为 true 时才成功。

请记住,在 CAS 失败时,“预期”(旧值第一个 arg)将更新为当前值flag;这就是为什么它通过引用(cppref)来获取它。另见了解 C++11 中的 std::atomic::compare_exchange_weak()

每次失败后,您的代码都会循环,直到它可以从上次迭代中看到的任何内容进行 CAS。 这很容易成功,并且您将很快在临界区中同时拥有两个线程,从而创建数据争用 UB(这会在多核系统上导致真正的问题,如果增量无法编译,甚至在单核系统上也是如此到一个指令。)


对于 C++ std::atomic:CAS 来说,通过指针而不是非const引用来获取 arg 可能是一个更好的设计,就像匹配 C11 接口的非成员 一样std::atomic_compare_exchange_weak(std::atomic<T>* obj, T* expected, T desired)。或者也许不是,但它会避免这个隐藏的陷阱。