1. 程式人生 > >C++併發實戰2:thread::join和thread::detach

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<iostream>
#include<boost/thread.hpp>
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<iostream>
#include<boost/noncopyable.hpp>
#include<boost/thread.hpp>
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<iostream>
#include<boost/thread.hpp>
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;
}