c++11中最重要的特性之一就是對多線程的支持了,然而《c++ primer》5th卻沒有這部份內容的介紹,着實人有點遺憾。在網上了解到了一些關於thread庫的內容。這是幾個比較不錯的學習thread庫的資源:ios
Thread support library : http://en.cppreference.com/w/cpp/threadc++
Cpp reference/thread : http://www.cplusplus.com/reference/thread/編程
<< C++ Concurrency In Action >> :http://files.cnblogs.com/files/ittinybird/CplusplusConcurrencyInAction.pdf //Recommend!windows
前兩個網站我仍是很是喜歡的,都是在線的幫助手冊,兩個選擇其中一個就能夠了,看你口味選擇就行了。最後一個是原版的《C++ Concurrency In Action》,很是棒的一本書,鑑於中文譯版已經被黑出翔了,因此能看英文就看英文版吧,我也是硬着頭皮啃了一點兒。如下是我學習thread庫的一點感覺和看法,若是你有發現個人錯誤,還請你及時批評指正,我將萬分感謝。安全
有關線程、併發相關的基礎知識,我就不浪費篇幅了。bash
c++11提供了一個新的頭文件<thread>提供了對線程函數的支持的聲明(其餘數據保護相關的聲明放在其餘的頭文件中,暫時先從thread頭文件入手吧),寫一個多線程的程序須要引用這個新的頭文件:多線程
#include <iostream> #include <thread> void fun() { std::cout << "A new thread!" << std::endl; } int main() { std::thread t(fun); t.join(); std::cout << "Main thread!" << std::endl; }
這樣的demo就是一個簡單的多線程的應用了。其輸出以下:併發
A new thread! Main thread!
所以咱們能夠猜想到它的執行流大體是這樣的:函數
那麼程序的執行流是如何從main()轉去執行fun()的呢,下面咱們先看看thread這個類。學習
個人環境是CentOS7 + g++4.8.3 頭文件/usr/include/c++/4.8.3/thread中有thread類的完整聲明(個人windows環境是win8.1+vs2013,在默認安裝的狀況下thread頭文件的路徑是C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\thread)。代碼太長,我就不貼出來了。c++線程庫經過構造一個線程對象,來啓動一個線程。那麼咱們就先來看一下thread的構造函數:
如下貼出thread類的代碼均是出自GNU的實現版本
class thread { ... public: thread() noexcept = default; thread(thread&) = delete; thread(const thread&) = delete; thread(thread&& __t) noexcept { swap(__t); } template<typename _Callable, typename... _Args> explicit thread(_Callable&& __f, _Args&&... __args) { _M_start_thread(_M_make_routine(std::__bind_simple( std::forward<_Callable>(__f), std::forward<_Args>(__args)...))); } ... };
這幾行代碼裏邊,卻有着大量的有關c++11特性的內容,右值引用、noexcept、=delete、以及可調用對象這些不是本篇博客要關注的點,所以就不詳細的展開了。咱們主要來看這個構造函數模板,
template<typename _Callable, typename... _Args>
explicit thread(_Callable&& __f, _Args&&... __args);
能夠清楚的看見它把參數都交給了std::__bind_simple()處理了,而對於std::__bind_simple()的定義在其頭文件<functional>中,和std::bind()的用法同樣,具體區別建議仍是看看頭文件比較好,這裏我就多個事,順手就貼出來了:
template<typename _Callable, typename... _Args> typename _Bind_simple_helper<_Callable, _Args...>::__type __bind_simple(_Callable&& __callable, _Args&&... __args) { typedef _Bind_simple_helper<_Callable, _Args...> __helper_type; typedef typename __helper_type::__maybe_type __maybe_type; typedef typename __helper_type::__type __result_type; return __result_type( __maybe_type::__do_wrap( std::forward<_Callable>(__callable)), std::forward<_Args>(__args)...); } template<typename _Result, typename _Func, typename... _BoundArgs> inline typename _Bindres_helper<_Result, _Func, _BoundArgs...>::type bind(_Func&& __f, _BoundArgs&&... __args) { typedef _Bindres_helper<_Result, _Func, _BoundArgs...> __helper_type; typedef typename __helper_type::__maybe_type __maybe_type; typedef typename __helper_type::type __result_type; return __result_type(__maybe_type::__do_wrap(std::forward<_Func>(__f)), std::forward<_BoundArgs>(__args)...); }
功力有限std::bind()具體實現我就不深究了,有機會我在研究研究。可是不難看出,這兩個函數的做用大致上就是封裝一個函數及其參數,返回一個__type類。thread在構造一個新的對象時,即是傳入了一個__type對象給_M_start_thread()實現啓動一個線程的。爲何要這樣作呢?咱們能夠認爲這是因爲OS的實現(我也是網上據說,若是你知道答案,不妨告訴我),用過Linux上的線程庫pthread的應該對pthread_create()中的start_routine參數有印象,它是一個函數指針,其對應的函數原型以下:
void* (*start_routine) (void*);
這樣就縮小了二者之間的差別,剩下的事就只要把__type的地址傳進去就能夠了。因爲使用的這樣的實現,std::thread()建立一個新的線程能夠接受任意的可調用對象類型(帶參數或者不帶參數),包括lambda表達式(帶變量捕獲或者不帶),函數,函數對象,以及函數指針。
上面咱們寫了一個不帶參數的demo,如今咱們就建立包含參數和捕獲的lambda表達式看看是否真的是這樣,demo:
#include <thread> #include <iostream> int main() { int n1 = 500; int n2 = 600; std::thread t([&](int addNum){ n1 += addNum; n2 += addNum; },500); t.join(); std::cout << n1 << ' ' << n2 << std::endl; }
獲得了預期結果:
[thread]main 1000 1100
在啓動了一個線程(建立了一個thread對象)以後,當這個線程結束的時候(std::terminate()),咱們如何去回收線程所使用的資源呢?thread庫給咱們兩種選擇:1.加入式(join()) 2.分離式(detach())。值得一提的是,你必須在thread
對象銷燬以前作出選擇,這是由於線程可能在你加入或分離線程以前,就已經結束了,以後若是再去分離它,線程可能會在thread
對象銷燬以後繼續運行下去。
Note that you only have to make this decision before the std::thread object is destroyed—the thread itself may well have finished long before you join with it or detach it, and if you detach it,then the thread may continue running long after the std::thread object is destroyed.-------《C++ Concurrency In Action》 2.1.1
join()字面意思是鏈接一個線程,意味着主動地等待線程的終止,上面的例子我都是使用了join()的方式。join()是這樣工做的,在調用進程中join(),當新的線程終止時,join()會清理相關的資源(any storage associated with the thread),而後返回,調用線程再繼續向下執行。正是因爲join()清理了線程的相關資源,於是咱們以前的thread對象與已銷燬的線程就沒有關係了,這意味着一個線程的對象每次你只能使用一次join(),當你調用的join()以後joinable()就將返回false了。光靠文字仍是略顯蒼白的,確定仍是代碼更加直觀:
#include <iostream> #include <thread> void foo() { std::this_thread::sleep_for(std::chrono::seconds(1)); } int main() { std::thread t(foo); std::cout << "before joining,joinable=" << std::boolalpha << t.joinable() << std::endl; t.join(); std::cout << "after joining, joinable=" << std::boolalpha << t.joinable() << '\n'; }
運行結果:
[thread]main before joining,joinable=true after joining, joinable=false
第二種方式是分離式,對應的函數是detach()。detach這個詞的意思是分離的意思,對一個thread對象使用detach()意味着從調用線程分理出這個新的線程,咱們稱分離的線程叫作守護線程(daemon threads)。以後也就不能再與這個線程交互。打個不太恰當的比方,就像是你和你女友分手(你可能說我好壞,爲何不說是我和個人女友?由於我沒有女友啊,哈哈,看我多機智。),那以後大家就不會再有聯繫(交互)了,而她的以後消費的各類資源也就不須要你去埋單了(清理資源)。既然沒有交互,也就談不上join()了,所以調用joinable()必然是返回false。分離的線程會在後臺運行,其全部權(ownership)和控制權將會交給c++運行庫。同時,C++運行庫保證,當線程退出時,其相關資源的可以正確的回收。
分離的線程,大體上是這樣執行的,它運行結束後,再也不須要通知調用它的線程:
在thread類中有一個叫作_M_id的私有變量用來標識線程,其類型是std::thread::id,在GNU上實現以下:
class thread { ... class id { native_handle_type _M_thread; public: id() noexcept : _M_thread() { } explicit id(native_handle_type __id) : _M_thread(__id) { } private: friend class thread; friend class hash<thread::id>; friend bool operator==(thread::id __x, thread::id __y) noexcept { return __gthread_equal(__x._M_thread, __y._M_thread); } friend bool operator<(thread::id __x, thread::id __y) noexcept { return __x._M_thread < __y._M_thread; }
template<class _CharT, class _Traits> friend basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id); }; private: id _M_id; public: thread::id get_id() const noexcept { return _M_id; } ... };
代碼仍是比較清晰的,很明顯咱們能夠經過std::this_thread::get_id()這個函數獲取線程的標識符,因爲上面的代碼中thread::id類中重載了operator 「<<」運算符,所以咱們能夠對id類型進行輸出。同時,當一個thread對象並無關聯一個線程的時候(可能thread對象是默認初始化的或者初始化的線程已經運行結束被join()或者是線程已經detach()),這時候get_id()將返回默認構造的id對象,意味着這個thread對象不存在關聯的線程,輸出可能像是這樣的:「thread::id of a non-executing thread」。與此同時,咱們也能夠在當前的線程中獲取當前線程的線程標識符,方法比較簡單直接調用std::this_thread::get_id()便可。
如今,咱們寫一個使用標準輸出嘗試輸出線程id的demo:
#include <iostream> #include <thread> void fun() { std::cout << std::this_thread::get_id() << std::endl; } int main() { std::thread t(fun); std::cout << t.get_id() << std::endl; t.join(); }
其輸出結果是一個15位的整數,具體取決於實現,固然具體怎麼實現並沒有區別,咱們只要關心它能夠做爲標識線程的一種手段:
[thread]main 140302328772352 140302328772352
同時,std::thread::id中還重載了operator==,這樣就容許咱們去比較兩個線程是否相等(是不是同一個線程),好比咱們須要給不一樣的線程分配任務或者限制某個線程的操做,id類型實現了這樣的比較運算給了咱們編程時極大的便利。
關於什麼時候用到std::thread::id::operator<,我暫時沒有搞清楚,若是您知道,不妨告訴我,我將萬分感激。
理解了以上內容,咱們基本可使用多線程去實現一些簡單的任務了,固然要想安全地使用線程,這仍是遠遠不夠的。接下來我還要再探、三探thread庫。
如若以上博文有錯誤、誤導之處,請你原諒,還望批評指正,我在此先謝過各位。