C++原子操作與內存序 1
問題
#include<iostream>
#include<thread>
int main()
{
int sum = 0;
auto f = [&sum]() {
for (int i = 0; i < 10000; i++)
sum += 1;
};
std::thread t1(f);
std::thread t2(f);
t1.join();
t2.join();
std::cout << "the sum of 2 threads is: " << sum << std::endl;
std::cin.get();
return 0;
}
這個程序只是簡單的通過兩個線程對同一個變量進行累加10000次,正常不管線程執行的先后順序,結果都應該是20000才對,可實際輸出結果如圖所示,程序的輸出3次的結果都不一樣,不一定是預期的20000;

分析
對于+1操作,具體執行可以分為3個操作,如下圖所示:

可以看出問題發生在兩個線程寫的時候,如線程1剛寫完,線程2繼續寫,則丟失一次加法。所以得出的值往往小于20000。
解決
可以通過std::mutex加鎖對變量操作進行保護,有沒有不用鎖也能實現的呢?C++中提供了原子操作可以實現這一目標。
代碼如下:
std::atomic<int> sum1 = 0;
auto f1 = [&sum1]() {
for (int i = 0; i < 10000; i++)
sum1+=1;
};
std::thread t3(f1);
std::thread t4(f1);
t3.join();
t4.join();
std::cout << "the sum of 2 threads with atomic is: " << sum1 << std::endl;
輸出如下:

可以看出未原子化的sum仍然是每次結果不盡相同,而原子化的sum1每次結果都為20000。
所謂原子操作指的是不可分割的操作,可以理解為只能編譯成一條單獨的CPU執行指令,不可以再分解,C++中,基本通過原子類型來實現原子操作。這種原子類型為std::atomic<T>,其中模板參數T為基本的數據類型,如bool,char,int,指針等。
程序中將sum1原子化,并調用+=操作符(已重載為原子操作),之前分解的3步成了不可分割的1步,所以不會出現兩個線程同時已經進入寫的狀態,進而能保證累加結果的正確。
注意事項
-
若累加操作改為sum1=sum1+1,就不是原子操作了,結果與sum沒有差別
-
int型++/+=是原子操作fetch_add()的重載,類似的還有fetch_sub()/fetch_and/fetch_or()/fetch_xor()
作者:robot2017
出處:http://www.rzrgm.cn/stephen2023/p/18106668
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任
出處:http://www.rzrgm.cn/stephen2023/p/18106668
版權:本文版權歸作者和博客園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任
浙公網安備 33010602011771號