[英]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_release
和memory_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.