繁体   English   中英

C++ atomic 用原子替换互斥锁是否安全<int> ?</int>

[英]C++ atomic is it safe to replace a mutex with an atomic<int>?

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;

const int FLAG1 = 1, FLAG2 = 2, FLAG3 = 3;
int res = 0;
atomic<int> flagger;

void func1() 
{
    for (int i=1; i<=1000000; i++) {
        while (flagger.load(std::memory_order_relaxed) != FLAG1) {}
        res++; // maybe a bunch of other code here that don't modify flagger
        // code here must not be moved outside the load/store (like mutex lock/unlock)
        flagger.store(FLAG2, std::memory_order_relaxed);
    }
    cout << "Func1 finished\n";
}

void func2() 
{
    for (int i=1; i<=1000000; i++) {
        while (flagger.load(std::memory_order_relaxed) != FLAG2) {}
        res++; // same
        flagger.store(FLAG3, std::memory_order_relaxed);
    }
    cout << "Func2 finished\n";
}

void func3() {
    for (int i=1; i<=1000000; i++) {
        while (flagger.load(std::memory_order_relaxed) != FLAG3) {}
        res++; // same
        flagger.store(FLAG1, std::memory_order_relaxed);
    }
    cout << "Func3 finished\n";
}

int main()
{
    flagger = FLAG1;
    
    std::thread first(func1);
    std::thread second(func2);
    std::thread third(func3);
    
    first.join();
    second.join();
    third.join();
    
    cout << "res = " << res << "\n";
    return 0;
}

我的程序有一个与此示例类似的段。 基本上,有 3 个线程:输入器、处理器和输出器。 我发现使用原子的忙等待比使用条件变量使线程进入睡眠状态要快,例如:

std::mutex commonMtx;
std::condition_variable waiter1, waiter2, waiter3;
// then in func1()
unique_lock<std::mutex> uniquer1(commonMtx);
while (flagger != FLAG1) waiter1.wait(uniquer1);

但是,示例中的代码安全吗? 当我运行它时,它会给出正确的结果( -std=c++17 -O3标志)。 但是,我不知道编译器是否可以将我的指令重新排序到原子检查/设置块之外,尤其是使用std::memory_order_relaxed 如果它不安全,那么有什么方法可以使它安全,同时比互斥锁更快?

编辑:保证线程数 < CPU 内核数

std::memory_order_relaxed不能保证 memory 操作的顺序,除了原子本身。

你所有的res++; 因此,操作是数据竞争,您的程序具有未定义的行为。

例子:

#include<atomic>

int x;
std::atomic<int> a{0};

void f() {
    x = 1;
    a.store(1, std::memory_order_relaxed);
    x = 2;
}

Clang 13 on x86_64 with -O2将此 function 编译为

    mov     dword ptr [rip + a], 1
    mov     dword ptr [rip + x], 2
    ret

https://godbolt.org/z/hxjYeG5dv

即使在缓存一致的平台上,在第一个和第二个mov之间,另一个线程也可以观察a设置为1 ,但x不设置为1

您必须使用memory_order_releasememory_order_acquire (或顺序一致性)来替换互斥锁。

(我应该补充一点,我没有详细检查你的代码,所以我不能说在你的具体情况下简单地替换 memory 订单就足够了。)

如另一个答案中所述, res++; 在不同的线程中彼此不同步,并导致数据竞争和未定义的行为。 这可以使用线程消毒剂进行检查。

要解决此问题,您需要使用memory_order_acquire进行加载,使用memory_order_release进行存储。 也可以使用线程消毒剂确认修复。

while (flagger.load(std::memory_order_acquire) != FLAG1) {}
res++;
flagger.store(FLAG2, std::memory_order_release);

或者, flagger.load(std::memory_order_acquire)可以替换为flagger.load(std::memory_order_relaxed) ,然后是std::atomic_thread_fence(std::memory_order_acquire); 循环之后。

while (flagger.load(std::memory_order_relaxed) != FLAG1) {}
std::atomic_thread_fence(std::memory_order_acquire);
res++;
flagger.store(FLAG2, std::memory_order_release);

我不确定它在多大程度上提高了性能,如果有的话。

这个想法是,只有循环中的最后一个加载需要是一个获取操作,这就是栅栏实现的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM