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++并发实战(C++11))