線程執行完入口函數,也會退出,在為一個線程創建一個std::thread對象后,需要等待這個線程結束。
線程在std::thread對象創建時啟動
構造std::thread對象,std::thread可以用可調用類型來構造: std::thread mythread(f) //會用f的構造函數去初始化mythread,替換默認的構造函數
std::thread mythread{f} //使用花括號更為保險,能避免語法解析,當傳入的是一個臨時變量,編譯器會將其解析為
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
函數聲明,而不是類型對象的定義
啟動線程后:有兩種:①:等待線程結束——加入式 命名對象.join()
②:讓其自主運行——分離式 命名對象.detach()
針對線程還沒結束,函數以及退出的時候,這時線程還持有函數局部變量的指針或引用;要使線程函數的功能齊全,將數據復制到線程中,而非復制到共享數據中
調用join():是簡單粗暴的等待線程完成,還清理了線程相關的存儲部分,這樣std::thread對象將不再與已經完成的線程有任何關聯,意味着一個線程只能使用一個join()
如果不想等待線程結束,可以分離線程,但是線程分離,就打破了線程與std::thread對象的聯系,不可能有std::therad對象能引用它,分離線程在后台運行不能被加入
分離線程:沒有任何顯式的用戶接口,在后台運行
不能對沒有執行線程的std::thread對象使用detach(),也是join的使用條件,當std::thread對象使用命名對象.joinable返回true,可以使用命名對象.detach()
向線程函數傳遞參數:
向std::thread構造函數中的可調用對象,或函數傳遞一個參數很簡單,注意:即使參數是引用形式,也可以在新線程中訪問
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
傳參數時,有時候會存在類型的隱式轉換,函數有可能在轉換成功之前奔潰,需要在構造前,即傳參時做類型轉換
void f(int i,std::string const& s);
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer,"%i",some_param);
std::thread t(f,3,std::string(buffer)); // 使用std::string(傳參時做的類型轉換),避免懸垂指針 ,懸垂指針:指向曾經的對象,但該對象已經不再存在了; 野指針:沒有被正確初始化,於是指向一個隨機的內存地址
t.detach();
}
線程的構造函數盲目的拷貝已提供的變量,構造函數無視原函數期待的函數類型,
當向線程中的對象傳遞函數且該函數本身包含引用,需要使用std::ref()將參數轉換成引用的形式
void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget,w,std::ref(data)); // 2
display_status();
t.join();
process_widget_data(data); // 3
}
上面講的都是傳函數,下面傳自己定義的類型,對象:
class X
{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x);
//初始化時,同時傳入所需參數
class X
{
public:
void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num); //std::thread構造函數的第三個參數是成員函數的第一個參數
上式中提供的參數可以移動,但是不能拷貝
每個實例(一個命名對象就是一個實例)負責管理一個執行線程,執行線程的所有權可以在多個std::thread實例中互相轉移,這時依賴於std::thread實例的可移動且不可復制性
不可復制性:保證了在同一時間點,一個std::thread只能關聯一個執行線程
可移動性:使程序員自己決定,哪個實例擁有實際執行線程的所有權
void some_function();
void some_other_function();
std::thread t1(some_function); // 1 執行實例化的t1
std::thread t2=std::move(t1); // 2 將t1轉移到t2 ,t1已經和執行線程沒什么關聯了
t1=std::thread(some_other_function); // 3 移動操作將會隱式調用,因為所有者是一個臨時對象
std::thread t3; // 4 t3使用默認方式創建,與任何執行線程都沒有關聯
t3=std::move(t2); // 5 將與t2關聯線程的所有權轉移到t3,因為t2是一個命名對象,需要顯示調用std::move
t1=std::move(t3); // 6 賦值操作將使程序崩潰,因為t1已經有一個關聯的線程,不能通過賦一個新值給std::thread對象的方式來“丟棄一個線程”
當一個線程沒有命名對象時,其所有者是個臨時對象
當線程的所有者是個臨時對象時,移動操作將會隱式調用
當線程的所有者是個命名對象時,移動操作要顯式調用
當命名對象有一個關聯線程時,不能通過賦值操作給它一個新線程,不允許通過賦一個新值給std::thread對象的方式來丟棄一個線程