2 线程启动、结束,创建线程方法,join、detach
文章目录
2.1 范例演示线程运行的开始和结束
每个程序至少有一个线程:执行main()函数的线程(为主线程),其余线程有其各自的入口函数。线程与主线程(线程以main()为入口函数的线程)
同时运行。如同main()函数执行完会退出一样,当线程执行完入口函数后,线程也会退出。在为一个线程创建了一个std::thread
对象后,需要等待这个线程结束;不过,线程需要先进行启动。下面就来启动线程。
-
包含一个头文件thread
#include <thread>
-
初始函数要写
void func() { cout << "子线程开始" << endl; cout << "do something..." << endl; cout << "子线程结束" << endl; }
-
main中写开始代码
int main() { thread t(func); return 0; }
代码2-1-1
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子线程开始" << endl;
cout << "do something..." << endl;
cout << "子线程结束" << endl;
}
int main() {
thread t(func);
return 0;
}
运行结果
如果主线程执行速度快于子线程的,将会是以下结果
libc++abi.dylib: terminating
Abort trap: 6
2.1.1 居然直接报错了,这是为什么呢?
主线程执行完毕,thread对象消亡时(即代码中的t对象),如果thread是joinable的(associated应该翻译为可关联的,也就是joinable的) ,析构函数会调用terminate(),terminate()会调用abort(), abort()是非正常结束进程,不进行任何清理工作,直接终止程序,其典型实现是输出标准错误流(即cerr使用的错误流)。如果thread被调用过join()或者detach(),那它就不是joinable的,所以不会引发terminate,进程正常终止。
2.1.2 “秀儿”的解决方法
修改代码,在main()函数中加入while(1)
代码2-1-2
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子线程开始" << endl;
cout << "do something..." << endl;
cout << "子线程结束" << endl;
}
int main() {
thread t(func);
while(1);
return 0;
}
这样,子线程一定会先执行完成,主线程卡在死循环里~,让我们看看运行结果吧~
运行结果
子线程开始
do something...
子线程结束
这下终于不会报错,且打出了我们想要的结果,但这样的程序肯定不是我们所想要的。
2.1.3 如何解决报错问题,利用detach()
代码2-1-3
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子线程开始" << endl;
cout << "do something..." << endl;
cout << "子线程结束" << endl;
}
int main() {
thread t(func);
t.detach();
return 0;
}
运行结果
有时候这样
子线程开始
do something...
子线程结束
有时候啥也没有
2.1.5 有时候有,有时候无这是为什么呢?
一旦detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行(主线程跟该子线程失去联系)这个子线程就相当于被C++运行时库接管了,当这个子线程执行完毕后,由运行时库负责清理该线程相关的资源。
2.1.6 怎么才能让程序正常运行,且输出想要的结果?利用join()
-
有关join()的解释
加入/汇合子线程,即阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,然后继续执行以下代码,如果主线程执行完毕,子线程没有执行完毕,这种程序是不合格的,写出来的程序也是不稳定的 一个书写良好的程序,应该是主线程,等待子线程执行完毕后,自己才能最终退出。
代码2-1-4
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子线程开始" << endl;
cout << "do something..." << endl;
cout << "子线程结束" << endl;
}
int main() {
thread t(func);
t.join();
return 0;
}
运行结果
子线程开始
do something...
子线程结束
2.1.7 如何判断线程是否能够join?利用joinable()
-
joinable()的解释
判断是否可以成功调用join或者detach,返回true和false,true:可以join和detach,false:不可以join和detach,且detach的线程,不可以再次join,join过的线程也不可以join。
2.2 其他创建线程的手法
2.2.1 用类,以及一个问题范例
如果用类作为一个参数传递给入口函数,类必须重载operator()构造函数,即向入口函数传递一个factor实例。
Attention!!
有件事需要注意,当把函数对象传入到线程构造函数中时,需要避免“最令人头痛的语法解析”(C++’s most vexing parse)。如果你传递了一个临时变量,而不是一个命名的变量;C++编译器会将其解析为函数声明,而不是类型对象的定义。
thread t(A());
这里相当与声明了一个名为A的函数,这个函数带有一个参数(函数指针指向没有参数并返回A对象的函数),返回一个std::thread
对象的函数,而非启动了一个线程。
使用在前面命名函数对象的方式,或使用多组括号①,或使用新统一的初始化语法②,可以避免这个问题。
thread t((A())); // 1
thread t{A()}; // 2
代码2-2-1
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int &m_i;
A(int &i):m_i(i) {
cout << "A的构造函数" << endl;
}
void operator() () {
cout << "线程开始" << endl;
cout << "m_i的值为:" << this->m_i << endl; // 产生不可预料的结果
cout << "线程结束" << endl;
}
};
int main() {
int num = 6;
A a(num);
thread t(a);
t.detach();
return 0;
}
运行结果
A的构造函数
2.2.2 为啥能安全地传递对象?
虽然未打印出6的值,但我们的关注点并不是这个(线程在后台已经执行了打印的语句,但是主线程先执行完毕,所以看不到输出结果),而是a作为主线程的局部变量,当主线程执行完毕,a变量释放,按一般情况会出错,真的会出错吗?
答案是:不会的~。为了进一步说明这个现象,在类中加入了拷贝构造函数和析构函数,并打印主线程对象的地址和子线程中this的值(即对象的地址)代码如下:
代码2-2-2
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int &m_i;
A(int &i):m_i(i) {
cout << "A的构造函数" << endl;
cout << "this's addr is: " << this << endl;
}
A(const A& a):m_i(a.m_i) {
cout << "A的拷贝构造函数" << " this's addr is: " << this << endl;
}
~A() {
cout << "A的析构函数" << " this's addr is: " << this << endl;
}
void operator() () {
cout << "线程开始" << endl;
cout << "m_i的值为:" << this->m_i << " this's addr is: " << this << endl;
cout << "线程结束" << endl;
}
};
int main() {
int num = 6;
cout << "num's addr is : " << &num << endl;
A a(num);
thread t(a);
t.join(); // 方便观察
return 0;
}
运行结果
A的构造函数
this's addr is: 0x7ffee0bb2710
a's addr is : 0x7ffee0bb2718
A的拷贝构造函数this's addr is: 0x7ffee0bb26f0
A的拷贝构造函数this's addr is: 0x7f8490402bd8
A的析构函数this's addr is: 0x7ffee0bb26f0
线程开始
m_i的值为:6 this's addr is: 0x7f8490402bd8
线程结束
A的析构函数this's addr is: 0x7f8490402bd8
A的析构函数this's addr is: 0x7ffee0bb2710
主线程中a对象的地址为0x7ffee45d8718
,最终执行输出对象的地址为0x7f8490402bd8
,**结论:由主线程传递给子线程的对象实际上是被是被复制到线程中去的。
2.2.3 用lambda表达式
代码2-2-3
#include <iostream>
#include <thread>
using namespace std;
int main() {
auto annoymous_func = [] {
cout << "线程开始" << endl;
cout << "do something" << endl;
cout << "线程结束" << endl;
};
thread t(annoymous_func);
t.join();
return 0;
}
运行结果
线程开始
do something...
线程结束