原地址: http://blog.csdn.net/hujingshuang/article/details/70208443
C++多線程支持庫(Thread support library)
C++的內置支持包括thread(線程),mutual exclusion(互斥),condition variables(條件變量)和future等。
頭文件有:
<thread>
<mutex>
<condition_variable>
<future>
先來說頭文件<thread>,這裏面有兩個東西,一個是thread類(class
thread),一個是this_thread命名空間(namespace this_thread,該命名空間中有幾個有用的函數),另外類thread中還有個內部類id(class
id)。
多線程舉例
通過一段多線程的代碼,我們一步步來揭開thread的面紗,代碼如下:
-
#include <iostream>
-
#include <thread>
-
-
using namespace std;
-
-
void task_one() {
-
for (int i = 0; i < 10; i++) {
-
cout << this_thread::get_id() << '\t' << i << endl;
-
this_thread::sleep_for(chrono::milliseconds(5));
-
}
-
}
-
-
void task_two(int n) {
-
for (int i = 0; i < n; i++) {
-
cout << this_thread::get_id() << '\t' << i << endl;
-
this_thread::sleep_for(chrono::milliseconds(10));
-
}
-
}
-
-
int main() {
-
int n = 20;
-
-
thread t1(task_one);
-
thread t2(task_two, n);
-
-
t1.join();
-
t2.join();
-
-
return 0;
-
}
上述代碼中,一共存在三個線程,t1,t2和程序主線程(也就是執行main的那個線程)。線程t1、t2的任務分別是執行task_one、task_two(也就是兩個函數),在各自的線程中打印線程id及循環量i。
另外代碼t1.join()和t2.join()在main函數中,也就是說在主線程中,表示將主線程與線程t1、t2相結合。這樣一來,主線程會阻塞,直到線程t1、t2執行完畢,主線程纔會執行後面的代碼。
線程的join與detach
在這裏要說明線程的兩種狀態:在任何一個時刻,線程是結合 或 分離 狀態:
1、一個結合狀態的線程能夠被其他線程回收資源和殺死,在被其他線程回收之前,它所佔有的資源是不釋放的;
2、一個分離狀態的線程是不能被其他線程回收或殺死的,它所佔有的資源會在該線程執行完畢後由系統自動釋放。
線程的結合和分離狀態決定了一個線程以什麼樣的方式來終止自己,在默認情況下線程是非分離的狀態。
OK,大家有了上述的概念,我們就可以開始看<thread>源碼了,代碼也不是很長,大家預覽一遍有個印象就可以了,分析在後面。(VS2013源碼在這裏:\Microsoft
Visual Studio 12.0\VC\include\thread)
-
-
class thread {
-
public:
-
class id;
-
typedef void *native_handle_type;
-
-
thread() _NOEXCEPT {
-
_Thr_set_null(_Thr);
-
}
-
-
template<class _Fn, class... _Args>
-
explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
-
_Launch(&_Thr, _STD bind(_Decay_copy(_STD forward<_Fn>(_Fx)), _Decay_copy(_STD forward<_Args>(_Ax))...));
-
}
-
-
~thread() _NOEXCEPT {
-
if (joinable())
-
_XSTD terminate();
-
}
-
-
thread(thread&& _Other) _NOEXCEPT : _Thr(_Other._Thr) {
-
_Thr_set_null(_Other._Thr);
-
}
-
-
thread& operator=(thread&& _Other) _NOEXCEPT {
-
return (_Move_thread(_Other));
-
}
-
-
thread(const thread&) = delete;
-
thread& operator=(const thread&) = delete;
-
-
void swap(thread& _Other) _NOEXCEPT {
-
_STD swap(_Thr, _Other._Thr);
-
}
-
-
bool joinable() const _NOEXCEPT {
-
return (!_Thr_is_null(_Thr));
-
}
-
-
void join();
-
-
void detach() {
-
if (!joinable())
-
_Throw_Cpp_error(_INVALID_ARGUMENT);
-
_Thrd_detachX(_Thr);
-
_Thr_set_null(_Thr);
-
}
-
-
id get_id() const _NOEXCEPT;
-
-
static unsigned int hardware_concurrency() _NOEXCEPT {
-
return (::Concurrency::details::_GetConcurrency());
-
}
-
-
native_handle_type native_handle() {
-
return (_Thr._Hnd);
-
}
-
-
private:
-
thread& _Move_thread(thread& _Other) {
-
if (joinable())
-
_XSTD terminate();
-
_Thr = _Other._Thr;
-
_Thr_set_null(_Other._Thr);
-
return (*this);
-
}
-
-
_Thrd_t _Thr;
-
};
源碼分析
成員變量
先來看thread類中唯一的一個私有成員變量,在代碼中提到了它是一個結構體,看下面的定義就明瞭了:
-
_Thrd_t _Thr;
-
-
typedef _Thrd_imp_t _Thrd_t;
-
-
typedef struct {
-
void *_Hnd;
-
unsigned int _Id;
-
} _Thrd_imp_t;
到這裏,我想大家心中終於有點着落了吧。
成員方法
現在來剖析剩下的thread方法。
1、thread::joinable()方法,其定義如下:
-
bool joinable() const _NOEXCEPT {
-
return (!_Thr_is_null(_Thr));
-
}
該方法判斷線程是否可結合,實質就是判斷線程id是否爲0。
2、thread::join()方法,其定義如下:
-
inline void thread::join(){
-
if (!joinable())
-
_Throw_Cpp_error(_INVALID_ARGUMENT);
-
if (_Thr_is_null(_Thr))
-
_Throw_Cpp_error(_INVALID_ARGUMENT);
-
if (get_id() == _STD this_thread::get_id())
-
_Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);
-
if (_Thrd_join(_Thr, 0) != _Thrd_success)
-
_Throw_Cpp_error(_NO_SUCH_PROCESS);
-
_Thr_set_null(_Thr);
-
}
由上述代碼和註釋可以知道,在以下幾種情況下,線程是不可結合的:
① 線程已經join()過了;
② 線程爲空線程;
③ 單個的線程,也就是線程自己與自己;
如果一個可結合的線程經過join後(等線程執行完畢後),會將線程id置爲0。
3、thread::detach()方法,定義如下:
-
void detach() {
-
if (!joinable())
-
_Throw_Cpp_error(_INVALID_ARGUMENT);
-
_Thrd_detachX(_Thr);
-
_Thr_set_null(_Thr);
-
}
好了,這裏幾個比較重要的方法和概念就分析完畢了。接下來介紹一下構造函數、析構函數及其他函數,最後會來總結一下。
4、析構函數,定義如下:
-
~thread() _NOEXCEPT {
-
if (joinable())
-
_XSTD terminate();
-
}
這個如果存在疑問,待會兒請看後面的總結。
5、構造函數
-
thread() _NOEXCEPT {
-
_Thr_set_null(_Thr);
-
}
-
-
template<class _Fn, class... _Args>
-
explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
-
_Launch(&_Thr, _STD bind(_Decay_copy(_STD forward<_Fn>(_Fx)), _Decay_copy(_STD forward<_Args>(_Ax))...));
-
}
-
-
thread(thread&& _Other) _NOEXCEPT : _Thr(_Other._Thr) {
-
_Thr_set_null(_Other._Thr);
-
}
-
-
thread& operator=(thread&& _Other) _NOEXCEPT {
-
return (_Move_thread(_Other));
-
}
-
-
thread(const thread&) = delete;
-
thread& operator=(const thread&) = delete;
對於構造函數和賦值函數,下面舉幾個列子大家就明白了:
-
int n = 20;
-
thread t1, t2;
-
-
-
thread t3(task_one);
-
thread t4(task_two, n);
-
-
thread t5(t3);
-
thread t6 = t4;
-
-
thread t7(move(t3));
-
thread t8 = move(t4);
6、其他方法
① thread::get_id()方法,獲取線程id;其定義如下:
-
inline thread::id thread::get_id() const _NOEXCEPT {
-
return (id(*this));
-
}
由於之前提到過id是個內部類,這個後面再分析。
② thread::swap()方法,線程交換;其定義如下:
-
void swap(thread& _Other) _NOEXCEPT {
-
_STD swap(_Thr, _Other._Thr);
-
}
-
template<class _Ty> inline
-
void swap(_Ty& _Left, _Ty& _Right) _NOEXCEPT_OP(is_nothrow_move_constructible<_Ty>::value && is_nothrow_move_assignable<_Ty>::value) {
-
_Ty _Tmp = _Move(_Left);
-
_Left = _Move(_Right);
-
_Right = _Move(_Tmp);
-
}
③ thread::hardware_concurrency()方法,這是個靜態方法,返回的是硬件線程上下文數量;其定義如下:
-
static unsigned int hardware_concurrency() _NOEXCEPT {
-
return (::Concurrency::details::_GetConcurrency());
-
}
④ thread::native_handle()方法,獲取線程的win32句柄;其定義如下:
-
native_handle_type native_handle() {
-
return (_Thr._Hnd);
-
}
最後還有一個私有方法,這個方法在拷貝構造函數(使用move)中調用。
⑤ thread::_Move_thread()方法,其定義如下:
-
thread& _Move_thread(thread& _Other) {
-
if (joinable())
-
_XSTD terminate();
-
_Thr = _Other._Thr;
-
_Thr_set_null(_Other._Thr);
-
return (*this);
-
}
總節
最後,我們來總結一下,其實重點要理解的就是線程的join、detach、joinable三者的關係:
我們再從thread的析構函數~thread()入手分析。
-
~thread() _NOEXCEPT {
-
if (joinable())
-
_XSTD terminate();
-
}
其實析構函數裏面只進行了判斷,並沒有析構什麼,因爲thread成員變量不存在用new或malloc進行內存分配的指針或數組,所以析構函數裏不做資源釋放工作。那麼爲什麼只能析構不可結合的線程呢?
這裏還是以博客最開始的代碼來分析,有主線程、t1、t2三個線程,如下。
-
int main() {
-
int n = 20;
-
-
thread t1(task_one);
-
thread t2(task_two, n);
-
-
t1.join();
-
t2.join();
-
-
cout << "main thread" << endl;
-
return 0;
-
}
我們可以總結一下線程不可結合(即joinable()爲false)的幾種情況:
① 空線程;
② move後的線程(即move(t),則t是不可結合的);
③ join後的線程;
④ detach後的線程;
在實例化了t1、t2對象之後,它們的狀態默認都是可結合的,如果現在直接調用它們的析構函數來析構它們,那麼在析構的時候線程處於什麼狀態呢?是執行完了嗎?還是正在執行呢?注意,如果一個在沒有結合(join)的情況下,就算它先於主線程執行完畢,其id依然是不爲0的。所以我們是不能確定其狀態的,所以我們只能析構明確了id爲0的線程。因爲id爲0的線程要麼已經執行完畢,要麼是空線程,要麼是分離後的線程。
另外,一個線程分離(detech)後,該線程對象邊便不能控制該線程,而是交由系統接管。
今天就到這裏。上面有些理解都是個人的理解和看法,或許有詞不達意或者錯誤的地方,希望大家能夠多多指教,謝謝!
補充說明:
voidFunc3() noexcept;
noexcept的功能相當於上面的throw(),表示函數不會拋出異常。如果noexcept修飾的函數拋出了異常,編譯器可以選擇直接調用std::terminate()終止程序運行。noexcept比throw()效率高一些。
voidFunc4() noexcept(常量表達式);
如果常量表達式的結果爲true,表示該函數不會拋出異常,反之則有可能拋出異常。不帶常量表達式的noexcept相當於noexcept(true)。