漫話C++0x(五)―- thread, mutex, condition_variable
熟悉C++98的朋友,應該都知道,在C++98中沒有thread, mutex, condition_variable這些與concurrency相關的特性支持,如果需要寫多線程相關程序,都要借助于不同平臺上各自提供的api,這樣帶來的問題就是程序的跨平臺移植性比較差,經常要用一大堆的#ifdef WIN32類似的宏來區分不同的平臺,搞得程序很難看。C++0x最原始的初衷之一就是為了讓C++的功能更加強大,更加方便使用。現如今硬件如此發達,concurrency在程序設計中已經是司空見慣的事情了,如果C++再不支持這些concurrency相關的特性,就真的out了。現在,C++程序員的福音到了,C++0x提供了對thread, mutex, condition_variable這些concurrency相關特性的支持,以后多線程這一塊的代碼可以完全跨平臺了,而且由于C++0x封裝的都比較好,代碼寫起來也十分簡潔。下面開始介紹今天的內容。
- 1. thread
寫過多線程程序的朋友,相信對thread本身都不會陌生,這里不對thread本身做太多的說明,以介紹C++0x中提供的thread的用法為主。請大家先看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#include < iostream> #include < string> #include < thread> class Printer { public: void Print(int id, std::string& name) { std::cout < < "id=" << id << ", name=" << name; } }; void Hello() { std::cout << "hello world" << std::endl; } int main() { Printer p; int id = 1; std::string name("xiao5ge"); std::thread t1(&Printer::Print, p, id, name); std::thread t2(std::bind(&Printer::Print, p, id, name)); std::thread t3([&]{ p.Print(id, name); }); std::thread t4(Hello); t4.join(); t3.join(); t2.join(); t1.join(); }
下面我們來通過分析上面的例子,來說明一下thread的用法。上面的t1-t4的四個例子,分別是thread的四種構造方式,我們一一來介紹一下:
- (1)這種方式是通過變參數模板實現的,第一個參數是線程入口函數的地址,后面的參數按函數調用時的參數順序傳入。這里需要說明兩點:一是變參模板也是C++0x新增的特性,將會在后面的文章中介紹;二是,類成員函數的第一個參數永遠是this,所以這里第一個參數放的是對象本身。
- (2)這種方式是傳入一個std::function對象,bind/function在前一篇中有介紹,不熟悉的朋友可以先看一下C++0x系列的第四篇。
- (3)這種方式是傳入一個lambda表達式,也即一個closure,是比較常用的方式。關于lambda也在上一篇中有介紹。
- (4)這種方式是最簡單,最常用的方式,直接傳入一個函數,寫過多線程程序的朋友應該對這種方式最為熟悉。
上面介紹了C++0x thread的基本用法,下面需要再補充幾點使用過程需要注意的事項:
- (1)如果入口函數的參數是以引用或指針形式傳入的,需要使用者保證在線程運行過程中,這些參數一直是有效的。同時,如果有多個線程會訪問或者修改這些變量,需要使用者做好同步,保證一致性。
- (2)關于上面提到的幾種構造方式,簡單的直接用4,復雜的推薦的選擇順序:1->3->2,即變參模板方式->lambda方式->bind方式
- (3)通常需要等子線程運行完,主線程才退出,所以在主線程中通常需要調各子線程的join()。
- 2. mutex
mutex實現的是“互斥鎖”的語義,在多線程的程序中,經常需要通過鎖的機制來保證數據的一致性,C++0x提供了下面四種語義的mutex:
- (1) std::mutex: 普通的互斥鎖,不能遞歸使用
- (2) std::timed_mutex:帶超時的互斥鎖,不能遞歸使用
- (3) std::recursive_mutex:遞歸互斥鎖
- (3) std::recursive_timed_mutex:帶超時的遞歸互斥鎖
關于mutex的使用,我們通常建議使用RAII(Resource Acquisition is Initialization)的方式,即在構造的時候lock, 析構的時候unlock, 不建議直接顯式的lock/unlock,因為這樣比較容易出錯。因此,C++0x也提供了兩個工具類std::lock_guard和std::unique_lock來輔助我們使用mutex,下面我們通過例子來看一下具體的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include < mutex> // global vars int data = 0; std::mutex data_mutex; // thread 1 { std::lock_guard< std::mutex> locker(data_mutex); data = 1; } // thread 2 { std::lock_guard< std::mutex> locker(data_mutex); data = 2; }
從上面的例子,相信大家可以對mutex的基本使用方法都應該比較清楚了,由于mutex本身就比較簡單,這里不再贅言。說一下std::unique_lock和std::lock_guard的區別,std::lock_guard只允許RAII方式的使用,而std::unique_lock可以在構造之后調用lock/unlock, 更加靈活一些,但使用的時候出錯的機率也更大一些,所以如果沒有什么特殊的需求,通常推薦盡量使用std::lock_guard.
- 3. condition_variable
關于condition_variable,它的語義是今天講的三個內容里面相對復雜一些的,我在之前也寫過一篇關于它的文章,不熟悉的朋友可以先閱讀一下《條件變量(Condition Variable)詳解》這篇文章,先了解一下條件變量,以方便理解后面的內容。我們知道,條件變量主要是用在多線程之間修改了shared_data之后的相互通信,由于條件變量在多線程編程中非常有用,所以C++0x也添加了對條件變量的支持,下面是C++0x提供的兩種不同類型的條件變量:
- (1)condition_variable: 用在std::unique_lock< std::mutex>上wait, 比較高效。
- (2)condition_variable_any: 可以用在任意mutex上wait, 比較靈活,但效率比condition_variable差一些。
下面我們通過例子來看看,條件變量在C++0x中的使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// global std::atomic< bool> is_finish(false); std::mutex finish_mutex; std::condition_variable finish_cond; // thread 1 { std::unique< std::mutex> locker(finish_mutex); // 1. loop wait while (!is_finish) { finish_cond.wait(locker); } // 2. wait until prediction is true, loop inside finish_cond.wait(locker, []{ return is_finish; }); // 3. wait until eithor prediction is true or timeout finish_cond.wait(locker, std::chrono::seconds(1), []{ return is_finish; }); } // thread 2 { is_finish = true; // 1. notify one of the waiter finish_cond.notify_one(); // 2. notify all the waiter finish_cond.notify_all(); }
上面的例子,基本覆蓋了C++0x提供的條件變量的主要用法。下面我們來一一分析一下,幫助大家更好的理解:
- (1)關于wait,有三種基本的用法:第1種是在指定的條件上循環等待,直到條件為真notify時才會繼續執行后面的邏輯;第2種用法語義上和第1種是一樣的,但是不是用戶做顯式的loop等待,用戶傳入一個需要滿足的條件的closure, wait一直等到這個條件為真被notify時才會返回繼續執行下面的邏輯,可以理解為這時候,在wait內部有一個loop;第3 種用法,加了一個超時的語義,wait一直等到條件為真或者超時,被notify時才會返回繼續執行下面的邏輯。
- (2)關于notify, 有兩種:第1種是notify_one, 只喚醒一個在wait的線程; 第2種是notify_all,喚醒所有在wait的線程,相當于一個broadcast的語義。
以上即是今天的主要內容,希望對正在學習C++0x的朋友有所幫助,榮幸之至!
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成