簡體   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