C++并发实战|C++并发实战2(thread::join和thread::detach)

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)】

    推荐阅读