原地址: 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)。