一、join的功能
假设有两个线程,线程A和线程B。线程A被托管在thread对象A中。在线程B中执行对象A的join()函数,那么线程B就会被阻塞住,直到线程A执行完成后,线程B才会执行A.join()后面的代码。
看代码:
void f(){
Sleep(1000);
cout<<"I am f thread"<<endl;
}
int main()
{
thread t1(f);
t1.join();
cout<<"run main thread"<<endl;
}
执行结果:
I am f thread
run main thread
但是如果创建一个空的thread对象
int main()
{
thread t1;
t1.join();
cout<<"run main thread"<<endl;
}
运行结果:
terminate called after throwing an instance of 'std::system_error'
what(): Invalid argument
进程已结束,退出代码3
上一篇博客中提到了,空的thread对象,其实是没有持有操作系统相关的资源,这种情况是不能join的。
二、joinable()函数
joinable()函数的功能,官方文档说的非常晦涩难懂。我个人比较倾向于这种解释:“判断当前的线程对象是否是可执行线程对象。”
一般有三种情况,joinable()返回false:
(1)该thread对象通过无参构造函数构造;
(2)该thread对象执行过join()或者detach();
(3)该thread对象被move过,但是move的接受方的joinable()结果为true。
看代码:
void f(){
}
int main()
{
thread t1;
thread t2(f);
t1=move(t2);
cout<<"t1 : "<<t1.joinable()<<endl;
cout<<"t2 : "<<t2.joinable()<<endl;
t1.join();
}
输出结果:
t1 : 1
t2 : 0
三、join函数到底干了什么?
一个非常容易忽略的点:join函数实际上是放弃了持有的线程资源。对于没有持有线程资源的thread对象不能join,这是为了保证thread对象只能join一次。joinable函数实际上是检测thread对象是否持有线程资源。
假如说我们创建了一个空的thread对象,没有传入函数,自然不会持有操作系统层面的线程资源。强行join就会报错。
假如我们给它move进来一个资源以后,那么它就可以join了。
这个对象在执行了join函数以后,彻底放弃了对于线程资源的管理权,joinable状态变成了false。
同样的道理,thread被move以后,放弃了线程资源,joinable状态变成了false。
一句话:joinable()返回值为true的对象,也就是持有线程资源的对象,才能join。
四、必须join或者detach吗?
从宏观上看,是的。
一句话描述:C++程序中,在thread对象执行析构函数的时候,不允许持有线程资源。执行析构之前,要么通过join放弃资源的控制权,要么将线程资源move出去。
~thread() _NOEXCEPT
{ // clean up
if (joinable())
_XSTD terminate();
}
从源码上看,直接以异常形式退出程序。
五、线程资源不能被覆盖
看代码:
void f(){
}
int main()
{
thread t1(f);
thread t2(f);
t1=move(t2); //覆盖t1持有的线程资源
cout<<"t1 : "<<t1.joinable()<<endl;
cout<<"t2 : "<<t2.joinable()<<endl;
t1.join();
}
执行结果:
terminate called without an active exception
进程已结束,退出代码3