C++11並發編程
為了讓你的代碼支持多線程庫,你環境上的編譯器必須支持 C++11,不過幸運的是現在絕大部分的編譯器都支持C++11了,除非你手頭上的編譯器版本是古董級別了,真這樣的話,還是建議你趕快升級,不光是為了這次的學習,後面你肯定會用上的。
在看本文之前,如果對什麽是線程、什麽是進程仍存有疑惑,請先Google之,這兩個概念不在本文的範圍之內。用多線程只有一個目的,那就是更好的利用cpu的資源,下面兩個圖很好的反應了這點。
以單cpu為例,假如有個進程,進程功能是從網絡上接受數據,將接收到的數據進行處理。我們知道,網絡上的數據是有時延的,甚至在你接收數據的這段時間內由於網絡等問題接收不到數據,如果是單線程的進程
如果進程含有兩個線程,一個線程專門接收數據,暫且叫做A線程吧;另外一個線程專門負責處理接收的數據,就叫B線程吧。那麽情況就不一樣了,當由於網絡等問題接收不到數據時,阻塞的只是A線程,完全可以通過某種調度機制將cpu調度給B線程讓其工作。這樣,cpu得到了最大的利用而不至於浪費在那兒。
好了,既然線程的用處這麽大,C++11又加了支持多線程的庫,那麽讓我們一窺其廬山真面目吧。
如何啟動一個線程
在C++11中,啟動一個新的線程非常簡單,當你創建一個 std::thread 的實例時,它便會自行啟動。同時在創建線程實例時,須提供給該線程一段將要執行的函數,方法之一是在創建線程實例傳遞一個函數指針。
好吧,不管學什麽編程語言,Hello world!感覺總是不會少的,這仿佛已經成為了一個標準。這次我們仍以經典的 "Hello world” 為例來向大家如何創建一個線程:
#include <thread> #include <iostream> void hello() { std::cout << "Hello world from thread !" << std::endl; } int main() { std::thread t(hello); t.join(); return 0; }
[root@xiaojianping c++]# g++ -std=c++11 -o a a.cpp [root@xiaojianping c++]# ./a terminate called after throwing an instance of 'std::system_error' what(): Enable multithreading to use std::thread: Operation not permitted 已放棄
雲行時報錯了,編譯的時候加上-lpthread
[root@xiaojianping c++]# g++ -std=c++11 -lpthread -o a a.cpp [root@xiaojianping c++]# ./a Hello world from thread !
看到了吧,首先,我們引入了頭文件<thread>。在這個頭文件中,C++11 提供了管理線程的類和函數。之後,我們定義了一個無返回類型的函數hello,這個函數除了在標準輸出流中打印一行文字之外什麽也不做。而後,我們定義了一個 std::thread 類型的變量 t,在定義的時候用hello函數名(其實就是一個函數指針)作為 std::thread 類構造函數的參數 。
這個例子中值得註意的是函數 join()。調用join()將導致當前線程等待被 join 的線程結束(在本例中即線程 main 必須等待線程 t結束後方可繼續執行)。如果你不調用 join() ,其結果是未定義的 —— 程序可能如你所願打印出 "Hello world from thread" 以及一個換行,但是也可能只打印出 "Hello world from thread" 卻沒有換行,甚至什麽都不做,那是因為線程 main 可能在線程 t結束之前就返回了。
其次還需註意的就是在編譯的時候需加上參數-std=c++11,否則編譯不過。
使用Lambda表達式啟動線程
如果線程所要執行的代碼非常短小時,你完全沒必要專門為之創建一個函數,取而代之的是使用 Lambda 表達式(關於Lambda 表達式,有興趣的朋友可以上網查查它的用法,它也是C++11新加入的特性)。我們可以很輕易地將上述例子改寫為使用 Lambda 表達式的形式:
#include <thread> #include <iostream> int main() { std::thread t([](){ std::cout << "Hello world from thread !"<< std::endl;}); t.join(); return 0; }
[root@xiaojianping c++]# ./b
Hello world from thread !
如上,我們使用了 Lambda 表達式替換掉原來的函數指針。不需要任何懷疑,這段代碼和之前使用函數指針的代碼實現了完全相同的功能。
是不是覺得開啟一個線程就是為了打印一句話有點小題大做是吧,好的,我們現在是該做點什麽了。在上個線程函數的基礎上,除了打印一句話,我們再加個功能:接受兩個參數,計算它們的和並打印出。
#include <thread> #include <iostream> //線程執行函數接受兩個int類型參數 void hello(int x,int y) { std::cout<<"Hello world from thread !" <<std::endl; std::cout<<"x+y=" <<x+y <<std::endl; } int main() { std::thread t (hello,10,20); t.join(); return 0; }
[root@xiaojianping c++]# ./c
Hello world from thread !
x+y=30
程序的運行結果正是我們想要的,沒錯,向線程函數傳參就是這麽簡單:在創建線程實例的時候和函數指針一起傳遞過去
區分線程
我們可以回一下我們是如何區分進程的?每個進程都有一個編號,稱為pid(進程id),我們就是通過它來區分不同的進程,不光是我們人,其實操作系統也是通過這個pid來區分管理不同的進程。
線程也一樣, 每個線程都有唯一的 ID 以便我們加以區分,我們稱之為tid(線程id)。使用 std::this_thread 類的 get_id() 便可獲取對應線程的tid。下面的例子將創建一些線程並使用 std::this_thread::get_id() 來獲取當前線程的tid,並打印出:
#include <thread> #include <iostream> #include <vector> void hello() { std::cout << "Hello world from thread: " << std::this_thread::get_id() << std::endl; } int main() { std::vector<std::thread> threads; for(int i = 0; i < 6; ++i) { threads.push_back(std::thread(hello)); } for(auto& thread : threads) { thread.join(); } return 0; }
[root@xiaojianping c++]# ./thread Hello world from thread: Hello world from thread: Hello world from thread: Hello world from thread: 140598791485184 140598816663296140598799877888 Hello world from thread: 140598783092480 Hello world from thread: 140598774699776 140598808270592 [root@xiaojianping c++]# ./thread Hello world from thread: 140198417712896 Hello world from thread: 140198409320192 Hello world from thread: 140198392534784 Hello world from thread: 140198375749376 Hello world from thread: 140198384142080 Hello world from thread: 140198400927488
看到沒,上述結果我肯定不是你所希望看到的,結果似乎混亂了,如果你在你的電腦上多運行幾遍,甚至還會出現其它各種結果,那麽這種匪夷所思的運行結果究竟是什麽原因導致的?
我們知道線程之間是交叉運行的,在上面這個例子,我們並沒有去控制線程的執行順序,某個線程在運行期間可能隨時被搶占, 同時可以看到,上面例子的ostream 分幾個步驟(首先輸出一個 string,然後是 ID,最後輸出換行),因此三個線程可能先都只執行了第一個步驟將 Hello world from thread 打印出來,然後每個線程都依次執行完剩余的兩個步驟(打印ID,然後換行),這就導致了上面的運行結果。
那麽有沒有方法解決上面的問題了,答案是肯定的,接下來我們將看到如何使用鎖機制控制線程的執行順序。
#include <thread> #include <iostream> #include <vector> pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void hello() { pthread_mutex_lock(&lock); std::cout << "Hello world from thread: " << std::this_thread::get_id() << std::endl; pthread_mutex_unlock(&lock); } int main() { std::vector<std::thread> threads; for(int i = 0; i < 6; ++i) { //pthread_mutex_lock(&lock); threads.push_back(std::thread(hello)); //pthread_mutex_unlock(&lock); } for(auto& thread : threads) { thread.join(); } return 0; }
[root@xiaojianping c++]# ./thread Hello world from thread: 140515972613888 Hello world from thread: 140515939043072 Hello world from thread: 140515930650368 Hello world from thread: 140515964221184 Hello world from thread: 140515955828480 Hello world from thread: 140515947435776 [root@xiaojianping c++]# ./thread Hello world from thread: 140654524454656 Hello world from thread: 140654532847360 Hello world from thread: 140654516061952 Hello world from thread: 140654507669248 Hello world from thread: 140654499276544 Hello world from thread: 140654490883840
看到沒有,現在再執行thread程序的時候,就不會亂了。
轉載了碼農有道,並把後部分完成了。
C++11並發編程