今天第一次接触C++并发编程,工具用书是《C++并发编程实战》,这本书翻译的非常好,比较尊重原著。
侧重join()函数的使用
先上代码:
#include<thread>
#include<iostream>
using namespace std;
void hello()
{
cout << "szu 801 科技楼!"<<endl;
}
void do_something()
{
cout << "wait for a minute!" << endl;
hello();
}
struct MyStruct
{
int i;
MyStruct(int i_):i(i_){}
void operator()()
{
for (unsigned j = 0;j < 100000; ++j)
{
do_something();
}
}
};
void oops()
{
int some_local_state = 0;
MyStruct my_struct(some_local_state);
std::thread my_thread(my_struct); //启动线程
my_thread.detach(); //不等待线程完成。函数退出
//my_thread.join();//函数退出前,线程执行完毕
}
int main()
{
oops();
return 0;
}
(1)在线程启动的时候,我们会涉及到调用线程的函数是等待线程结束后函数退出,还是不等待。如果不等待(detach()函数),那么上述代码中的oops()函数的局部变量函数结束销毁后还在被线程使用,这是不可取的。我们调用join()表示等待线程结束在退出函数。
(2)join()函数简单而粗暴,要么等,要么就是不等。调用join()函数的行为会清理所有与线程相关联的存储器,这样该线程将不与任何其他的线程相关联。一旦调用,该std::thread()是不可连接的。joinable()将返回false。
(3)如果要分离线程,那么在启动线程之后立即调用detach()。如果打算等待该线程,就要仔细选择在哪个位置调用join()。
(4)但是问题出现了,如果在线程启动之后但又在调用join()之前使用引发了异常,那么join()调用就容易被跳过。这时候我们避免这种情况发生的有效方法之一是使用标准的资源获取即初始化(RAII)惯用语法。并提供一个类,在他的析构函数中进行join()。
代码举例:
class thread_guard //使用RAII等待线程完成
{
std::thread &t;
public:
explicit thread_guard(std::thread &t_) :
t(t_){}
~thread_guard()
{
if (t.joinable()) //如果线程还没结束
{
t.join(); //就等待线程完成
}
}
thread_guard(thread_guard const&) = delete;//禁止复制 声明为已经被删除了
thread_guard operator=(thread_guard const&) = delete;// 禁止赋值操作 ,
};
void f()
{
int some_local_state = 0;
MyStruct my_struct(some_local_state);
std::thread t(my_struct); //启动线程
thread_guard g(t);
do_something(); //执行到这里时,局部对象会按照析构函数逆序销毁
}
int main()
{
f();
/*<span style="white-space:pre"> </span>thread_guard a();
<span style="white-space:pre"> </span>thread_guard b(a);//这个操作是不合法的*/
return 0;
}
(5)如果无需等待线程完成,可以通过调用detach()来避免这种异常的安全问题,这样就确保thread对象被销毁时,std::terminate()不会被调用,此时线程一般运行在后台。