C++併發實戰2:thread::join和thread::detach
阿新 • • 發佈:2019-01-09
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;
}