C++多线程

来源:互联网 发布:翻倍牛股指标源码 编辑:程序博客网 时间:2024/05/20 08:25

原地址: 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的面纱,代码如下:

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <thread>  
  3.   
  4. using namespace std;  
  5.   
  6. void task_one() {  
  7.     for (int i = 0; i < 10; i++) {  
  8.         cout << this_thread::get_id() << '\t' << i << endl;  
  9.         this_thread::sleep_for(chrono::milliseconds(5));    // 休眠5ms  
  10.     }  
  11. }  
  12.   
  13. void task_two(int n) {  
  14.     for (int i = 0; i < n; i++) {  
  15.         cout << this_thread::get_id() << '\t' << i << endl;  
  16.         this_thread::sleep_for(chrono::milliseconds(10));   //休眠10ms  
  17.     }  
  18. }  
  19.   
  20. int main() {  
  21.     int n = 20;  
  22.   
  23.     thread t1(task_one);  
  24.     thread t2(task_two, n);  
  25.   
  26.     t1.join();  
  27.     t2.join();  
  28.   
  29.     return 0;  
  30. }  
        上述代码中,一共存在三个线程,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)

[cpp] view plain copy
  1. // 管理线程的类  
  2. class thread {  
  3. public:  
  4.     class id;                   // 内部类,后面会分析  
  5.     typedef void *native_handle_type;  
  6.   
  7.     thread() _NOEXCEPT {        // 构造函数,空线程  
  8.         _Thr_set_null(_Thr);    // 宏定义,原型为:#define _Thr_set_null(thr) (thr._Id = 0)  
  9.     }  
  10.   
  11.     template<class _Fn, class... _Args>  
  12.     explicit thread(_Fn&& _Fx, _Args&&... _Ax) {    // 带参模板构造函数_Fx(_Ax...)  
  13.         _Launch(&_Thr, _STD bind(_Decay_copy(_STD forward<_Fn>(_Fx)), _Decay_copy(_STD forward<_Args>(_Ax))...));  
  14.     }  
  15.   
  16.     ~thread() _NOEXCEPT {       // 析构函数  
  17.         if (joinable())         // 线程是可结合的,析构异常(也就是说只能析构不可结合的线程)  
  18.             _XSTD terminate();  // terminate会调用abort()来终止程序  
  19.     }  
  20.   
  21.     thread(thread&& _Other) _NOEXCEPT : _Thr(_Other._Thr) {     // 拷贝构造函数,调用move  
  22.         _Thr_set_null(_Other._Thr);  
  23.     }  
  24.   
  25.     thread& operator=(thread&& _Other) _NOEXCEPT {  // 赋值函数,调用move  
  26.         return (_Move_thread(_Other));  
  27.     }  
  28.   
  29.     thread(const thread&) = delete;                 // 禁用 拷贝构造函数  
  30.     thread& operator=(const thread&) = delete;      // 禁用 赋值函数  
  31.   
  32.     void swap(thread& _Other) _NOEXCEPT {           // 交换两线程  
  33.         _STD swap(_Thr, _Other._Thr);  
  34.     }  
  35.   
  36.     bool joinable() const _NOEXCEPT {               // 若线程可结合程,返回 true;否则,返回flase  
  37.         return (!_Thr_is_null(_Thr));               // 宏定义,原型为:#define _Thr_is_null(thr) (thr._Id == 0)  
  38.     }  
  39.   
  40.     void join();                                    // 线程结合,阻塞的  
  41.   
  42.     void detach() {                                 // 线程分离  
  43.         if (!joinable())                            // 若线程是不可结合的,则异常  
  44.             _Throw_Cpp_error(_INVALID_ARGUMENT);  
  45.         _Thrd_detachX(_Thr);  
  46.         _Thr_set_null(_Thr);  
  47.     }  
  48.   
  49.     id get_id() const _NOEXCEPT;                    // 获取线程唯一 id  
  50.   
  51.     static unsigned int hardware_concurrency() _NOEXCEPT {      // 返回硬件线程上下文数量  
  52.         return (::Concurrency::details::_GetConcurrency());  
  53.     }  
  54.   
  55.     native_handle_type native_handle() {            // 以 void* 形式返回线程的 Win32 句柄  
  56.         return (_Thr._Hnd);  
  57.     }  
  58.   
  59. private:  
  60.     thread& _Move_thread(thread& _Other) {          // move from _Other  
  61.         if (joinable())  
  62.             _XSTD terminate();  
  63.         _Thr = _Other._Thr;  
  64.         _Thr_set_null(_Other._Thr);  
  65.         return (*this);  
  66.     }  
  67.   
  68.     _Thrd_t _Thr;            // 私有成员变量,_Thrd_t是一个结构体,后面会分析  
  69. };  

源码分析

成员变量

        先来看thread类中唯一的一个私有成员变量,在代码中提到了它是一个结构体,看下面的定义就明了了:

[cpp] view plain copy
  1. _Thrd_t _Thr; //其实_Thrd_t 是类型的别名  
  2.   
  3. typedef _Thrd_imp_t _Thrd_t;    // 而_Thrd_imp_t是一个结构体  
  4.   
  5. typedef struct {    /* 线程 Win32 标识符 */  
  6.     void *_Hnd;     /* Win32 句柄 */  
  7.     unsigned int _Id;    // 线程id  
  8. } _Thrd_imp_t;  
        到这里,我想大家心中终于有点着落了吧。

成员方法

        现在来剖析剩下的thread方法。


1、thread::joinable()方法,其定义如下:

[cpp] view plain copy
  1. bool joinable() const _NOEXCEPT {       // 若线程可结合程,返回 true;否则,返回flase  
  2.     return (!_Thr_is_null(_Thr));       // 宏定义,原型为:#define _Thr_is_null(thr) (thr._Id == 0)  
  3. }  

该方法判断线程是否可结合,实质就是判断线程id是否为0。


2、thread::join()方法,其定义如下:

[cpp] view plain copy
  1. inline void thread::join(){ // join thread  
  2.     if (!joinable())            // 线程不可结合  
  3.         _Throw_Cpp_error(_INVALID_ARGUMENT);  
  4.     if (_Thr_is_null(_Thr))     // 空线程  
  5.         _Throw_Cpp_error(_INVALID_ARGUMENT);  
  6.     if (get_id() == _STD this_thread::get_id()) // 线程不能与自己结合  
  7.         _Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);  
  8.     if (_Thrd_join(_Thr, 0) != _Thrd_success)   // 线程结合(_Thrd_join()是join方法的核心),是阻塞的  
  9.         _Throw_Cpp_error(_NO_SUCH_PROCESS);  
  10.     _Thr_set_null(_Thr);        // 设置线程id为0  
  11. }  
由上述代码和注释可以知道,在以下几种情况下,线程是不可结合的:

        ① 线程已经join()过了;

        ② 线程为空线程;

        ③ 单个的线程,也就是线程自己与自己;

如果一个可结合的线程经过join后(等线程执行完毕后),会将线程id置为0。


3、thread::detach()方法,定义如下:

[cpp] view plain copy
  1. void detach() { // detach thread  
  2.     if (!joinable())        // 线程不可结合  
  3.         _Throw_Cpp_error(_INVALID_ARGUMENT);  
  4.     _Thrd_detachX(_Thr);    // 线程分离(detach的核心)  
  5.     _Thr_set_null(_Thr);    // 设置线程id为0  
  6. }  

好了,这里几个比较重要的方法和概念就分析完毕了。接下来介绍一下构造函数、析构函数及其他函数,最后会来总结一下。


4、析构函数,定义如下:

[cpp] view plain copy
  1. ~thread() _NOEXCEPT {       // 析构函数  
  2.     if (joinable())         // 线程是可结合的,析构异常(也就是说只能析构不可结合的线程,即id为0线程;id不为0的线程不能析构)  
  3.         _XSTD terminate();  // terminate会调用abort()来终止程序  
  4. }  

这个如果存在疑问,待会儿请看后面的总结。


5、构造函数

[cpp] view plain copy
  1. thread() _NOEXCEPT {        // 构造函数,空线程  
  2.     _Thr_set_null(_Thr);    // 宏定义,原型为:#define _Thr_set_null(thr) (thr._Id = 0)  
  3. }  
  4.   
  5. template<class _Fn, class... _Args>  
  6. explicit thread(_Fn&& _Fx, _Args&&... _Ax) {    // 带参模板构造函数_Fx(_Ax...)  
  7.     _Launch(&_Thr, _STD bind(_Decay_copy(_STD forward<_Fn>(_Fx)), _Decay_copy(_STD forward<_Args>(_Ax))...));  
  8. }  
  9.   
  10. thread(thread&& _Other) _NOEXCEPT : _Thr(_Other._Thr) {     // 拷贝构造函数,调用move  
  11.     _Thr_set_null(_Other._Thr);  
  12. }  
  13.   
  14. thread& operator=(thread&& _Other) _NOEXCEPT {  // 赋值函数,调用move  
  15.     return (_Move_thread(_Other));  
  16. }  
  17.   
  18. thread(const thread&) = delete;                 // 禁用 拷贝构造函数  
  19. thread& operator=(const thread&) = delete;      // 禁用 赋值函数  

对于构造函数和赋值函数,下面举几个列子大家就明白了:

[cpp] view plain copy
  1. int n = 20;  
  2. thread t1, t2;          // 正确,空线程  
  3.   
  4. // 带参的构造函数,先写线程执行的函数,后面有多少参数就跟多少个  
  5. thread t3(task_one);    // 正确,0个参数  
  6. thread t4(task_two, n); // 正确,1个参数  
  7.   
  8. thread t5(t3);          // 错误,使用了被禁用的拷贝构造函数  
  9. thread t6 = t4;         // 错误,使用了被禁用的赋值函数  
  10.   
  11. thread t7(move(t3));    // 正确,使用move,t7与t3功能相同,但t3被move之后变成了空线程  
  12. thread t8 = move(t4);   // 正确,使用move,t8与t3功能相同,但t3被move之后变成了空线程  


6、其他方法

        ① thread::get_id()方法,获取线程id;其定义如下:

[cpp] view plain copy
  1. inline thread::id thread::get_id() const _NOEXCEPT {  
  2.     return (id(*this));  
  3. }  

        由于之前提到过id是个内部类,这个后面再分析。


        ② thread::swap()方法,线程交换;其定义如下:

[cpp] view plain copy
  1. void swap(thread& _Other) _NOEXCEPT {  
  2.     _STD swap(_Thr, _Other._Thr);  
  3. }  

[cpp] view plain copy
  1. template<class _Ty> inline  
  2. void swap(_Ty& _Left, _Ty& _Right) _NOEXCEPT_OP(is_nothrow_move_constructible<_Ty>::value && is_nothrow_move_assignable<_Ty>::value) {  
  3.     _Ty _Tmp = _Move(_Left);  
  4.     _Left = _Move(_Right);  
  5.     _Right = _Move(_Tmp);  
  6. }  


        ③ thread::hardware_concurrency()方法,这是个静态方法,返回的是硬件线程上下文数量;其定义如下:

[cpp] view plain copy
  1. static unsigned int hardware_concurrency() _NOEXCEPT {  
  2.     return (::Concurrency::details::_GetConcurrency());  
  3. }  

       

        ④ thread::native_handle()方法,获取线程的win32句柄;其定义如下:

[cpp] view plain copy
  1. native_handle_type native_handle() {    // 其中:typedef void *native_handle_type;  
  2.     return (_Thr._Hnd);  
  3. }  


最后还有一个私有方法,这个方法在拷贝构造函数(使用move)中调用。

        ⑤ thread::_Move_thread()方法,其定义如下:

[cpp] view plain copy
  1. thread& _Move_thread(thread& _Other) {  // move from _Other  
  2.     if (joinable())  
  3.         _XSTD terminate();  
  4.     _Thr = _Other._Thr;  
  5.     _Thr_set_null(_Other._Thr);    //线程id置为0  
  6.     return (*this);  
  7. }  


总节

        最后,我们来总结一下,其实重点要理解的就是线程的join、detach、joinable三者的关系:

        我们再从thread的析构函数~thread()入手分析。

[cpp] view plain copy
  1. ~thread() _NOEXCEPT {       // 析构函数  
  2.     if (joinable())         // 线程是可结合的,析构异常(也就是说只能析构不可结合的线程)  
  3.         _XSTD terminate();  // terminate会调用abort()来终止程序  
  4. }  
        其实析构函数里面只进行了判断,并没有析构什么,因为thread成员变量不存在用new或malloc进行内存分配的指针或数组,所以析构函数里不做资源释放工作。那么为什么只能析构不可结合的线程呢?

        这里还是以博客最开始的代码来分析,有主线程、t1、t2三个线程,如下。

[cpp] view plain copy
  1. int main() {  
  2.     int n = 20;  
  3.   
  4.     thread t1(task_one);  
  5.     thread t2(task_two, n);  
  6.   
  7.     t1.join();  
  8.     t2.join();  
  9.   
  10.     cout << "main thread" << endl;  
  11.     return 0;  
  12. }  

        我们可以总结一下线程不可结合(即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)。


原创粉丝点击