C++11中的原子操作(atomic operation)
所謂的原子操作,取的就是“原子是最小的、不可分割的最小個體”的意義,它表示在多個線程訪問同一個全局資源的時候,能夠確保所有其他的線程都不在同一時間內訪問相同的資源。也就是他確保了在同一時刻只有唯一的線程對這個資源進行訪問。這有點類似互斥對象對共享資源的訪問的保護,但是原子操作更加接近底層,因而效率更高。
在以往的C++標准中並沒有對原子操作進行規定,我們往往是使用匯編語言,或者是借助第三方的線程庫,例如intel的pthread來實現。在新標准C++11,引入了原子操作的概念,並通過這個新的頭文件提供了多種原子操作數據類型,例如,atomic_bool,atomic_int等等,如果我們在多個線程中對這些類型的共享資源進行操作,編譯器將保證這些操作都是原子性的,也就是說,確保任意時刻只有一個線程對這個資源進行訪問,編譯器將保證,多個線程訪問這個共享資源的正確性。從而避免了鎖的使用,提高了效率。
我們還是來看一個實際的例子。假若我們要設計一個廣告點擊統計程序,在服務器程序中,使用多個線程模擬多個用戶對廣告的點擊:
#include <boost/thread/thread.hpp>
#include <atomic>
#include <iostream>
#include <time.h>
using namespace std;
// 全局的結果數據
long total = 0;
// 點擊函數
void click()
{
for(int i=0; i<1000000;++i)
{
// 對全局數據進行無鎖訪問
total ++;
}
}
int main(int argc, char* argv[])
{
// 計時開始
clock_t start = clock();
// 創建100個線程模擬點擊統計
boost::thread_group threads;
for(int i=0; i<100;++i)
{
threads.create_thread(click);
}
threads.join_all();
// 計時結束
clock_t finish = clock();
// 輸出結果
cout<<"result:"<<total<<endl;
cout<<"duration:"<<finish -start<<"ms"<<endl;
return 0;
}
從執行的結果來看,這樣的方法雖然非常快,但是結果不正確
E:\SourceCode\MinGW>thread.exe
result:87228026
duration:528ms
很自然地,我們會想到使用互斥對象來對全局共享資源的訪問進行保護,於是有了下面的實現:
long total = 0;
// 對共享資源進行保護的互斥對象
mutex m;
void click()
{
for(int i=0; i<1000000;++i)
{
// 訪問之前,鎖定互斥對象
m.lock();
total++;
// 訪問完成后,釋放互斥對象
m.unlock();
}
}
互斥對象的使用,保證了同一時刻只有唯一的一個線程對這個共享進行訪問,從執行的結果來看,互斥對象保證了結果的正確性,但是也有非常大的性能損失,從剛才的528ms變成了現在的8431,用了原來時間的10多倍的時間。這個損失夠大。
E:\SourceCode\MinGW>thread.exe
result:100000000
duration:8431ms
如果是在C++11之前,我們的解決方案也就到此為止了,但是,C++對性能的追求是永無止境的,他總是想盡一切辦法榨干CPU的性能。在C++11中,實現了原子操作的數據類型(atomic_bool,atomic_int,atomic_long等等),對於這些原子數據類型的共享資源的訪問,無需借助mutex等鎖機制,也能夠實現對共享資源的正確訪問。
// 引入原子數據類型的頭文件
#include <atomic>
// 用原子數據類型作為共享資源的數據類型
atomic_long total(0);
//long total = 0;
void click()
{
for(int i=0; i<1000000;++i)
{
// 僅僅是數據類型的不同而以,對其的訪問形式與普通數據類型的資源並無區別
total ++;
}
}
我們來看看使用原子數據類型之后的效果如何:
E:\SourceCode\MinGW>thread.exe
result:100000000
duration:2105ms
結果正確!耗時只是使用mutex互斥對象的四分之一!也僅僅是不采用任何保護機制的時間的4倍。可以說這是一個非常不錯的成績了。
原子操作的實現跟普通數據類型類似,但是它能夠在保證結果正確的前提下,提供比mutex等鎖機制更好的性能,如果我們要訪問的共享資源可以用原子數據類型表示,那么在多線程程序中使用這種新的等價數據類型,是一個不錯的選擇。
i++和++i是否為原子操作
一.i++
i++的操作分三步:
(1)棧中取出i
(2)i自增1
(3)將i存到棧
所以i++不是原子操作,上面的三個步驟中任何一個步驟同時操作,都可能導致i的值不正確自增
二.++i
在多核的機器上,cpu在讀取內存i時也會可能發生同時讀取到同一值,這就導致兩次自增,實際只增加了一次。
綜上,我認為i++和++i都不是原子操作。