thread::join()是个简单暴力的方法,主线程等待子进程期间什么都不能做,一般情形是主线程创建thread object后做自己的工作而不是简单停留在join上。thread::join()还会清理子线程相关的内存空间,此后thread object将不再和这个子线程相关了,即thread object不再joinable了,所以join对于一个子线程来说只可以被调用一次,为了实现更精细的线程等待机制,可以使用条件变量等机制。
异常环境下join,假设主线程在一个函数f()里面创建thread object,接着f()又调用其它函数g(),那么确保在g()以任何方式下退出主线程都能join子线程。如:若g()通过异常退出,那么f()需要捕捉异常后join
#include
#include
void do_something(int& i){
i++;
}
class func{
public:
func(int& i):i_(i){}
void operator() (){
for(int j=0;
j<100;
j++)
do_something(i_);
}
public:
int& i_;
};
void do_something_in_current_thread(){}
void f(){
int local=0;
func my_func(local);
boost::thread t(my_func);
try{
do_something_in_current_thread();
}
catch(...){
t.join();
//确保在异常条件下join子线程
throw;
}
t.join();
}
int main(){
f();
return 0;
}
上面的方法看起来笨重,有个解决办法是采用RAII(资源获取即初始化),将一个thread object通过栈对象A管理,在栈对象A析构时调用thread::join.按照局部对象析构是构造的逆序,栈对象A析构完成后再析构thread object。如下:
#include
#include
#include
using namespace std;
class thread_guard:boost::noncopyable{
public:
explicit thread_guard(boost::thread& t):t_(t){}
~thread_guard(){
if(t_.joinable()){//检测是很有必要的,因为thread::join只能调用一次,要防止其它地方意外join了
t_.join();
}
}
//thread_guard(const thread_guard&)=delete;
//c++11中这样声明表示禁用copy constructor需要-std=c++0x支持,这里采用boost::noncopyable已经禁止了拷贝和复制
//thread_guard& operator=(const thread_guard&)=delete;
private:
boost::thread& t_;
};
void do_something(int& i){
i++;
}
class func{
public:
func(int& i):i_(i){}
void operator()(){
for(int j=0;
j<100;
j++)
do_something(i_);
}
public:
int& i_;
};
void do_something_in_current_thread(){}
void fun(){
int local=0;
func my_func(local);
boost::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
}
int main(){
fun();
return 0;
}
说明:禁止拷贝和复制的原因是防止栈对象thread_guard在超出了thread object对象生命期的地方使用。如果detach一个线程则没有上述这么麻烦,必经detach后就不管子线程了。
thread::detach()后:没有直接方法与线程通信,不可能wait了,不可能有任何thread object指向这个线程。线程变成了后台进程(孤儿进程),在Linux将由init接管,在c++中由库接管。若不确定一个线程是否有thread object指向它,那么请先用thread::joinable()检测后再thread::detach()就想前面代码中的joinable检测一样。
考虑一个情形:通常我们在word中编辑文件A时,点击"新建"按钮后会出现新的窗口继续编辑文件B。这里A和B是相对独立的,A"新建"相当于开启一个线程去供B使用,其后A马上detach这个线程。代码如下:
#include
#include
void open_document_and_display_gui(const std::string& filename){}//“新建”这一操作
bool done_eaditing(){//是否完成编辑
return true;
}
enum command_type{open_new_document};
//用户命令,这里只有一个就是“新建”
class user_command{//用户命令封装
public:
user_command():type(open_new_document){}
public:
command_type type;
};
user_command get_user_input(){//获取用户命令,这里就是获取"新建“
return user_command();
}
std::string get_filename_from_user(){
return "foo.doc";
}
void process_user_input(const user_command& cmd){}//处理用户的普通输入,即文档编写void edit_document(const std::string& filename){//假设初始时文档A执行edit_document,发现用户有”新建“命令到达,则开启一个线程去供文档B使用,且立即detach这个线程
open_document_and_display_gui(filename);
while(!done_eaditing()){
user_command cmd=get_user_input();
if(cmd.type==open_new_document){
const std::string new_name=get_filename_from_user();
boost::thread t(edit_document,new_name);
//文档B也是采用同样的线程函数,这里可以看出thread可以接收参数,还有一个方法参考pthread_create将参数封装在一个函数对象中,然后传给thread
t.detach();
//立即执行detach,这里可以肯定thread t和线程相关,故不需要检测joinable
}
else
process_user_input(cmd);
//普通文档编辑
}
}
int main(){
edit_document("bar.doc");
return 0;
}
【C++并发实战|C++并发实战2(thread::join和thread::detach)】