c++11提供了一個新的頭文件<thread>提供了對線程函數的支持的聲明(其他數據保護相關的聲明放在其他的頭文件中,暫時先從thread頭文件入手吧),寫一個多線程的程序需要引用這個新的頭文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#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就是一個簡單的多線程的應用了。其輸出如下:
1
2
|
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的實現版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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()的用法一樣,具體區別建議還是看看頭文件比較好,這里我就多個事,順手就貼出來了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#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;
}
|
得到了預期結果:
1
2
|
[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了。光靠文字還是略顯蒼白的,肯定還是代碼更加直觀:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#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'
;
}
|
運行結果:
1
2
3
|
[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上實現如下:
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
|
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; }
<br>
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#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位的整數,具體取決於實現,當然具體怎么實現並無區別,我們只要關心它可以作為標識線程的一種手段:
1
2
3
|
[thread]main
140302328772352
140302328772352
|
同時,std::thread::id中還重載了operator==,這樣就允許我們去比較兩個線程是否相等(是否是同一個線程),比如我們需要給不同的線程分配任務或者限制某個線程的操作,id類型實現了這樣的比較運算給了我們編程時極大的便利。