C++11 线程

来源:互联网 发布:淘宝商城男鞋休闲潮鞋 编辑:程序博客网 时间:2024/06/09 23:53

C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是<atomic>,<thread>,<mutex>,<condition_variable><future>

·        <atomic>:该头文主要声明了两个类, std::atomic std::atomic_flag,另外还声明了一套 C风格的原子类型和与 C兼容的原子操作的函数。

·        <thread>:该头文件主要声明了 std::thread类,另外 std::this_thread命名空间也在该头文件中。

·        <mutex>:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard,std::unique_lock,以及其他的类型和函数。

·        <condition_variable>:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable std::condition_variable_any

·        <future>:该头文件主要声明了 std::promise, std::package_task两个 Provider类,以及 std::futurestd::shared_future两个 Future 类,另外还有一些与之相关的类型和函数,std::async()函数就声明在此头文件中。

 c++11 中创建线程非常简单:

C++11支持Lambda表达式,因此一个新线程的回调函数也可以是有一个Lambda表达式的形式,但是注意如果使用Lambda表达式最好不要使用引用的方式,应该使用值传递的方式来访问数据,在多线程中使用引用容易造成混乱。


[cpp] view plain copy
  1. #include <iostream>  
  2. #include <thread>  
  3. using namespace  std;  
  4.   
  5. void thread_func()  
  6. {  
  7.     //...  
  8. }  
  9.   
  10. void thread_func2(int i)  
  11. {  
  12.     // ...  
  13. }  
  14.   
  15.   
  16. int main(int argc, _TCHAR* argv[])  
  17. {  
  18.     thread t1{ thread_func };  
  19.     thread t2{ thread_func2, 5 };  
  20.   
  21.     return 0;  
  22. }  

        首先包含头文件<thread>,这是c++11中新添加的类。然后用函数名和参数来构造一个thread对象就可以了。构造完成后线程会自动执行。下面是构造函数的参数形势:

[cpp] view plain copy
  1. template<class _Fn,  
  2.         class... _Args>  
  3.         explicit thread(_Fn&& _Fx, _Args&&... _Ax)  

        第一个参数是一个函数,后面是一个可变长参数表。


        除了创建线程,一般还会有几个相配套的控制线程的系统函数。

                1、Join函数

                2、Detach函数


在vs2013中编译运行上面的测试代码,发现会发生运行时错误!为什么呢?

         如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process(僵尸进程),即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid) 。

一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

因为在多线程情况下,无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死(部分线程保持一种终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态),在第一个例子的输出中,可以看到子线程还来不及执行完毕,主线程的main()函数就已经执行完毕,从而所有子线程终止。需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但千万要记住的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。终止态的线程,仍旧作为一个线程实体存在与操作系统中。(这点在win和unix中是一致的。)而什么时候销毁线程,取决于线程属性。

         通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源之外,其线程函数所拥有的资源(申请的动态内存,打开的文件,打开的网络端口等)也不一定能释放。所以,针对这个问题,主线程和子线程之间通常定义两种关系:
        1、可会合(joinable)。这种关系下,主线程需要明确执行等待操作。在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合。这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程,Thread类中,这个操作通过在主线程的线程函数内部调用子线程对象的jion()函数实现。必须强调的是,即使子线程能够在主线程之前执行完毕,进入终止态,也必需显示执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源(线程id或句柄,线程管理相关的系统资源)也永远不会释放。
        2、相分离(detached)。顾名思义,这表示子线程无需和主线程会合,也就是相分离的。这种情况下,子线程一旦进入终止态,系统立即销毁线程,回收资源。无需在主线程内调用wait()实现会合。Thread类中,调用detach()使线程进入detached状态。这种方式常用在线程数较多的情况,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或者不可能的。所以在并发子线程较多的情况下,这种方式也会经常使用。detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。

        缺省情况下,创建的线程都是可会合的。可会合的线程可以通过调用detach()方法变成相分离的线程。但反向则不行。


        我们的主线程中既没有调用jion函数来等待子线程结束,也没用调用detach来将其交给操作系统管理。最终很可能导致进程僵死,资源无法释放!c++11对这种情况视为不合法,出现这种情况,c++11就会调用terminate函数来终止我们的进程。而在vs中调用terminate函数就会出现运行时错误对话框!


        所以,要记住:对于每一个子线程,要么就调用join,要么就调用detach。否则存在安全隐患,视为不合法!

0 0
原创粉丝点击