所謂的原子操作,取的就是“原子是最小的、不可分割的最小個(gè)體”的意義,它表示在多個(gè)線程訪問(wèn)同一個(gè)全局資源的時(shí)候,能夠確保所有其他的線程都不在同一時(shí)間內(nèi)訪問(wèn)相同的資源。也就是他確保了在同一時(shí)刻只有唯一的線程對(duì)這個(gè)資源進(jìn)行訪問(wèn)。這有點(diǎn)類似互斥對(duì)象對(duì)共享資源的訪問(wèn)的保護(hù),但是原子操作更加接近底層,因而效率更高。
在以往的C++標(biāo)準(zhǔn)中并沒(méi)有對(duì)原子操作進(jìn)行規(guī)定,我們往往是使用匯編語(yǔ)言,或者是借助第三方的線程庫(kù),例如intel的pthread來(lái)實(shí)現(xiàn)。在新標(biāo)準(zhǔn)C++11,引入了原子操作的概念,并通過(guò)這個(gè)新的頭文件提供了多種原子操作數(shù)據(jù)類型,例如,atomic_bool,atomic_int等等,如果我們?cè)诙鄠€(gè)線程中對(duì)這些類型的共享資源進(jìn)行操作,編譯器將保證這些操作都是原子性的,也就是說(shuō),確保任意時(shí)刻只有一個(gè)線程對(duì)這個(gè)資源進(jìn)行訪問(wèn),編譯器將保證,多個(gè)線程訪問(wèn)這個(gè)共享資源的正確性。從而避免了鎖的使用,提高了效率。
我們還是來(lái)看一個(gè)實(shí)際的例子。假若我們要設(shè)計(jì)一個(gè)廣告點(diǎn)擊統(tǒng)計(jì)程序,在服務(wù)器程序中,使用多個(gè)線程模擬多個(gè)用戶對(duì)廣告的點(diǎn)擊:
#include <boost/thread/thread.hpp>
#include <atomic>
#include <iostream>
#include <time.h>
using namespace std;
// 全局的結(jié)果數(shù)據(jù)
long total = 0;
// 點(diǎn)擊函數(shù)
void click()
{
for(int i=0; i<1000000;++i)
{
// 對(duì)全局?jǐn)?shù)據(jù)進(jìn)行無(wú)鎖訪問(wèn)
total += 1;
}
}
int main(int argc, char* argv[])
{
// 計(jì)時(shí)開(kāi)始
clock_t start = clock();
// 創(chuàng)建100個(gè)線程模擬點(diǎn)擊統(tǒng)計(jì)
boost::thread_group threads;
for(int i=0; i<100;++i)
{
threads.create_thread(click);
}
threads.join_all();
// 計(jì)時(shí)結(jié)束
clock_t finish = clock();
// 輸出結(jié)果
cout<<"result:"<<total<<endl;
cout<<"duration:"<<finish -start<<"ms"<<endl;
return 0;
}
從執(zhí)行的結(jié)果來(lái)看,這樣的方法雖然非??欤墙Y(jié)果不正確
E:SourceCodeMinGW>thread.exe
result:87228026
duration:528ms
很自然地,我們會(huì)想到使用互斥對(duì)象來(lái)對(duì)全局共享資源的訪問(wèn)進(jìn)行保護(hù),于是有了下面的實(shí)現(xiàn):
long total = 0;
// 對(duì)共享資源進(jìn)行保護(hù)的互斥對(duì)象
mutex m;
void click()
{
for(int i=0; i<1000000;++i)
{
// 訪問(wèn)之前,鎖定互斥對(duì)象
m.lock();
total += 1;
// 訪問(wèn)完成后,釋放互斥對(duì)象
m.unlock();
}
}
互斥對(duì)象的使用,保證了同一時(shí)刻只有唯一的一個(gè)線程對(duì)這個(gè)共享進(jìn)行訪問(wèn),從執(zhí)行的結(jié)果來(lái)看,互斥對(duì)象保證了結(jié)果的正確性,但是也有非常大的性能損失,從剛才的528ms變成了現(xiàn)在的8431,用了原來(lái)時(shí)間的10多倍的時(shí)間。這個(gè)損失夠大。
E:SourceCodeMinGW>thread.exe
result:100000000
duration:8431ms
如果是在C++11之前,我們的解決方案也就到此為止了,但是,C++對(duì)性能的追求是永無(wú)止境的,他總是想盡一切辦法榨干CPU的性能。在C++11中,實(shí)現(xiàn)了原子操作的數(shù)據(jù)類型(atomic_bool,atomic_int,atomic_long等等),對(duì)于這些原子數(shù)據(jù)類型的共享資源的訪問(wèn),無(wú)需借助mutex等鎖機(jī)制,也能夠?qū)崿F(xiàn)對(duì)共享資源的正確訪問(wèn)。
// 引入原子數(shù)據(jù)類型的頭文件
#include <atomic>
// 用原子數(shù)據(jù)類型作為共享資源的數(shù)據(jù)類型
atomic_long total(0);
//long total = 0;
void click()
{
for(int i=0; i<1000000;++i)
{
// 僅僅是數(shù)據(jù)類型的不同而以,對(duì)其的訪問(wèn)形式與普通數(shù)據(jù)類型的資源并無(wú)區(qū)別
total += 1;
}
}
我們來(lái)看看使用原子數(shù)據(jù)類型之后的效果如何:
E:SourceCodeMinGW>thread.exe
result:100000000
duration:2105ms
結(jié)果正確!耗時(shí)只是使用mutex互斥對(duì)象的四分之一!也僅僅是不采用任何保護(hù)機(jī)制的時(shí)間的4倍??梢哉f(shuō)這是一個(gè)非常不錯(cuò)的成績(jī)了。
原子操作的實(shí)現(xiàn)跟普通數(shù)據(jù)類型類似,但是它能夠在保證結(jié)果正確的前提下,提供比mutex等鎖機(jī)制更好的性能,如果我們要訪問(wèn)的共享資源可以用原子數(shù)據(jù)類型表示,那么在多線程程序中使用這種新的等價(jià)數(shù)據(jù)類型,是一個(gè)不錯(cuò)的選擇。
聯(lián)系客服