#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 內核數
uj5u.com熱心網友回復:
std::memory_order_relaxed 除了原子本身之外,無法保證記憶體操作的順序。
res ;因此,您的所有操作都是資料競爭,并且您的程式具有未定義的行為。
例子:
#include<atomic>
int x;
std::atomic<int> a{0};
void f() {
x = 1;
a.store(1, std::memory_order_relaxed);
x = 2;
}
x86_64 上的 Clang 13-O2將此函式編譯為
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(或順序一致性)來替換互斥鎖。
(我應該補充一點,我沒有詳細檢查你的代碼,所以我不能說在你的特定情況下簡單地替換記憶體順序就足夠了。)
uj5u.com熱心網友回復:
正如另一個答案中提到的,res ;在不同的執行緒中彼此不同步,并導致資料競爭和未定義的行為。這可以使用執行緒消毒劑進行檢查。
要解決此問題,您需要使用memory_order_acquireto load 和memory_order_releasefor store。也可以使用執行緒消毒劑確認修復。
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);
我不確定它在多大程度上提高了性能,如果有的話。
這個想法是只有回圈中的最后一個加載需要是一個獲取操作,這就是柵欄實作的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/399887.html
上一篇:在run方法中列印實體
下一篇:如何在kivy的執行緒上放置影片
