二、线程创建、结束

一、使用函数创建线程

1、thread()

创建一个线程入口函数,子线程从这个函数开始运行,函数结束,线程也就结束了。主线程执行完毕,代表整个进程执行完毕。

如果子线程还没执行完毕,主线程先执行完了,一般情况下,这些子线程会被强行终止,这时使用join()函数,使主线程等待子线程执行完毕后,主线程与子线程会汇合,然后主线程继续走下去。

1.#include <iostream>
2.#include <string>
3.#include <thread> //线程
4using namespace std;
56//创建的线程,也就是一个初始函数
7void myprint(){
8.    cout<<"1 begin" <<endl;
9.    cout<<"2 end"<<endl;
10.}
1112int main(){
13.    thread myobj(myprint);//创建线程,线程就已经开始执行了
14.    myobj.join();//阻塞主线程,让主线程等子线程执行完,然后主线程继续往下走
15//如果不写join,可能main先执行完了,子线程还没执行完,会报错
16.    cout<<"main end"<<endl;
17return 0;
18.}

2、detach

分离,主线程不用等子线程了,主线程执行主线程的,子线程执行子线程的,不汇合了。(一般不用detach)

原因:创建了很多子线程,让主线程逐个等待子线程结束不太好,就提出了detach。

一旦thread对象调用了detach函数,这个thread对象就失去了与主线程的联系,子线程就会驻留在后台运行,由c++运行时库接管,子线程结束后,运行时库负责清理线程相关的资源(守护线程)。

 1 1.#include <iostream>  
 2 2.#include <thread> //线程  
 3 3.using namespace std;  
 4 4.  
 5 5.//创建的线程,也就是一个初始函数  
 6 6.void myprint(){  
 7 7.    cout<<"1 begin" <<endl;  
 8 8.    cout<<"2 end"<<endl;  
 9 9.}  
10 10.  
11 11.int main(){  
12 12.    thread myobj(myprint);//创建线程,线程就已经开始执行了  
13 13.    myobj.detach();  //主线程和子线程分离,各走各的。
14 14.    cout<<"main end"<<endl;  
15 15.    return 0;  
16 16.}  

一旦用了detach,就不能再用join了!

3、joinable()

thread对象调用joinable()函数,判断是否可以成功使用join或detach,返回true或false。

一般可以用在使用join或detach之前,用来提前先判断,如果true,可以使用,否则不能使用。

二、用类创建线程

thread参数除了函数名,还可以是类对象来作为线程入口。

用类对象创建线程,这个对象实际上是被默认复制到线程中的(也就是在创建thread对象时,这个对象已经创了一个副本,在子线程中的是副本而不是本身),执行完主线程后,这个类对象会被回收,但是副本在子线程中,直到子线程结束,这个副本才会被回收,只要你这个类对象里面没有引用、指针,就不会产生问题

当类对象里面有引用时:

class A{
public:
    int& m_i;
    A(int& i):m_i(i){}//构造函数
    void operator()(){//可以实现将对象当函数来使用
        cout<<"m_i的值为"<<m_i<<endl;
    }
}

int main(){
    int i=9;
    A a(i);
    thread thread1(a);//a是类对象,用类对象创建子线程
    //thread1.join();用join没问题
    thread1.detach();//会出问题!
    cout<<"main end"<<endl;
    return 0;
}

用detach的话,主线程与子线程分离了,有可能出现一种情况:

  • 主线程先执行完,那么a是属于主线程栈空间的,此时a被回收。但由于A构造函数是引用,所以指向了一个已经销毁的内存,类似于野指针,会出错。

而使用join(),主线程会等子线程执行完在走,所以不会出现这个问题。

改成join后,程序流程为:

  1. 创建类对象ta,构造函数被执行;
  2. 创建子线程的同时就开始运行了,a对象副本给了子线程,副本构造函数执行,引用的主线程里的i;
  3. 堵塞主线程,等子线程运行完;
  4. 子线程结束,a对象副本执行析构函数,子线程释放内存;
  5. 主线程继续执行;
  6. main函数代码跑完了,主线程执行完毕,a对象析构函数执行,释放内存和变量a;

三、用lambda表达式

 1 #include <iostream>
 2 #include <thread> //线程
 3 using namespace std;
 4 
 5 int main(){
 6     auto mythread = []{
 7         cout<<"我的子线程开始了"<<endl;
 8         cout<< "结束了"<<endl;
 9     };
10     thread myobj(mythread);
11     myobj.join();
12     cout<<"main end"<<endl;
13     return 0;
14 }

TA构造函数执行,创建线程myobj,这个ta对象时复制进去的,子线程中使用的是副本。

原文地址:https://www.cnblogs.com/pacino12134/p/11227877.html