1. 程式人生 > >C++11中原子操作atomic

C++11中原子操作atomic

所謂的原子操作,取的就是“原子是最小的、不可分割的最小個體”的意義,它表示在多個執行緒訪問同一個全域性資源的時候,能夠確保所有其他的執行緒都不在同一時間內訪問相同的資源。也就是他確保了在同一時刻只有唯一的執行緒對這個資源進行訪問。這有點類似互斥物件對共享資源的訪問的保護,但是原子操作更加接近底層,因而效率更高。
在以往的C++標準中並沒有對原子操作進行規定,我們往往是使用匯編語言,或者是藉助第三方的執行緒庫,例如intel的pthread來實現。在新標準C++11,引入了原子操作的概念,並通過這個新的標頭檔案提供了多種原子操作資料型別,例如,atomic_bool,atomic_int等等,如果我們在多個執行緒中對這些型別的共享資源進行操作,編譯器將保證這些操作都是原子性的,也就是說,確保任意時刻只有一個執行緒對這個資源進行訪問,編譯器將保證,多個執行緒訪問這個共享資源的正確性。從而避免了鎖的使用,提高了效率。
std::atomic對int, char, bool等資料結構進行原子性封裝,在多執行緒環境中,對std::atomic物件的訪問不會造成競爭-冒險。利用std::atomic可實現資料結構的無鎖設計。

我們還是來看一個實際的例子。假若我們要設計一個廣告點選統計程式,在伺服器程式中,使用多個執行緒模擬多個使用者對廣告的點選:
#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 += 1;     
    }
}
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 += 1;
        // 訪問完成後,釋放互斥物件 
        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 += 1;
    }
}
我們來看看使用原子資料型別之後的效果如何:
E:\SourceCode\MinGW>thread.exe
result:100000000
duration:2105ms
結果正確!耗時只是使用mutex互斥物件的四分之一!也僅僅是不採用任何保護機制的時間的4倍。可以說這是一個非常不錯的成績了。
原子操作的實現跟普通資料型別類似,但是它能夠在保證結果正確的前提下,提供比mutex等鎖機制更好的效能,如果我們要訪問的共享資源可以用原子資料型別表示,那麼在多執行緒程式中使用這種新的等價資料型別,是一個不錯的選擇。