C++11在標準庫中爲多線程提供組件, 使用線程須要包含頭文件 thread, 其命名空間爲 std.多線程
每一個進程至少有一個線程: 執行main()函數的線程, 其他線程有其各自的入口函數(線程函數)。
當線程執行完線程函數後, 線程也會退出. 若是不傳入線程函數(相似這種形式std::thread t;), 線程不會運行. 線程函數不能重載, 不然不能編譯.
在爲一個線程建立了一個 std::thread 對象後, 若是線程已啓動(不傳入線程序函數時, 線程不會啓動), 必需要明確是加入(join)仍是分離線程(detach).函數
// 啓動一個線程: void MyThread(const std::string& str) { PRINT_LINE_INFO(); std::cout << str << std::endl; } //std::thread t(MyThread, "Hello C..."); std::thread t([] { MyThread("Hello C..."); MyThread("Hello C2..."); }); // 對於類方法, 須要使用 std::bind. std::thread t(std::bind(&ThreadExample::MyThread, this, "msg")); ThreadGuard tg(t);
若是 std::thread 對象銷燬以前尚未調用 join 或 detach, 程序就會終止( std::thread 的析構函數會調用 std::terminate() ). 所以, 即使是有異常存在, 也須要確保線程可以正確的加入(joined)或分離(detached).
調用 join 或 detach 以前須要調用 joinable() 判斷一下線程是否運行. 若是 joinable() 返回 false, 則不須要.
join()是簡單粗暴的等待線程完成, 此時建立 std::thread 對象的線程(如下稱主線程)將被阻塞. 若是在線程啓動後到主線程在調用 join() 前的代碼中發生了異常, 此時將會致使主線程永遠沒有機會執行.
針對此問題, 須要使用 RAII 機制來解決, 如建立一個 ThreadGuard 對象, 在析構函數中保證老是能夠調用到 join.oop
#ifndef _THREAD_GUARD_ #define _THREAD_GUARD_ #include <thread> class ThreadGuard { public: ThreadGuard(std::thread& t_) : t(t_){} ~ThreadGuard() { if (t.joinable()) { t.join(); } } ThreadGuard(const ThreadGuard &) = delete; ThreadGuard& operator=(const ThreadGuard &) = delete; private: std::thread& t; }; #endif // _THREAD_GUARD_
若是是分離線程, 必須保證可訪問數據的有效性, 不然會產生未定義的行爲, 如同單線程中一個對象被銷燬後再訪問同樣.
處理這種狀況的常規方法: 使線程函數的功能齊全, 將數據複製到線程中. 若是使用一個可調用的對象做爲線程函數,這個對象就會複製到線程中,然後原始對象就能夠銷燬. 下面是錯誤的使用方法示例:this
class Func { int& i; public: Func(int& i_) : i(i_) {} void operator() () { for (unsigned j = 0; j < 10; ++j) { // 潛在訪問隱患:懸空引用 i std::cout << i << " "; } std::cout << std::endl; } }; { // 某個做用域內 int* p = new int(100); Func f(*p); std::thread t(f); t.detach(); // 不等待線程結束 delete p; } // 新線程可能還在運行
線程函數能夠有不一樣的參數, 向線程傳遞參數,只要在構造 std::thread 對象時,按照線程函數參數列表一一對應傳入便可。線程函數有幾點須要注意的地方:spa
(1) 默認的參數會被拷貝到獨立的線程中,即便是引用的形式, 若是須要須要傳遞引用, 須要使用 std::ref 顯示說明(而且線程函數參數也須要聲明爲引用).線程
void ThreadParamRef(std::string& str) { str += " --> add"; } void ThreadParam(std::string str) { str += " --> add"; } std::string str("Hello C++ Thread..."); //std::thread t(ThreadParamRef, str); std::thread t(ThreadParamRef, std::ref(str)); // 只有這種形式才能在線程執行完畢後輸出 Hello C++ Thread... --> add //std::thread t(ThreadParam, std::ref(str)); t.join(); std::cout << str << std::endl;
(2) 線程參數傳遞時須要注意不能傳入局部變量, 考慮下面的代碼,buffer②是一個指針變量,指向本地變量,而後本地變量經過buffer傳遞到新線程中②。
函數有很大的可能,會在字面值轉化成 std::string 對象以前崩潰,從而致使線程的一些未定義行爲。
解決方案就是在傳遞到 std::thread 構造函數以前就將字面值轉化爲 std::string 對象。指針
void f(int i,std::string const& s); void oops(int some_param) { char buffer[1024]; // 1 sprintf(buffer, "%i",some_param); std::thread t(f,3,buffer); // 2 t.detach(); } // 正確的方法 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(); }
(3) 線程函數參數傳遞時, 能夠移動, 但不能拷貝. "移動"是指: 原始對象中的數據轉移給另外一對象,而轉移的這些數據在原始對象中再也不保存.code
void ThreadParamUniquePtr(std::unique_ptr<int> up) { std::cout << (up.get() ? *up : -1) << std::endl; } std::thread t(ThreadParamUniquePtr, std::move(up)); //std::thread t(ThreadParamUniquePtr, up); // 不能編譯 //std::thread t(ThreadParamUniquePtr, std::ref(up)); // 要求線程函數參數也爲引用才能編譯 t.join(); std::cout << (up.get() ? *up : -1) << std::endl; // 將輸出-1
線程是資源獨佔型, 但能夠將全部權轉移給別的對象. 若是一個 std::thread 對象與一個運行的線程關聯, 此時接受一個新的線程全部權時, 其之前關聯的線程將直接調用 std::terminate() 終止程序繼續運行.對象
std::thread t1(f);
std::thread t2(f);
// t1 = std::thread(f); // t1 全部權尚未轉移, 不能經過賦一個新值來放棄線程
// t1 = std::move(t2); // t1 全部權尚未轉移, 不能經過賦一個新值來放棄線程
t1.detach(); 或 t1.join();
t1 = std::move(t2);
std::thread t3 = std::move(t1);
t1 = std::move(t2);blog
線程對象也能夠在函數中進行轉移.
std::thread f1()
{
return std::thread(f);
}
std::thread f2()
{
std::thread t(f);
return t;
}
void f3(std::thread t);
void f4()
{
f3(std::thread(f));
std::thread t(f);
f3(std::move(t));
}
因爲 std::thread 是可轉移的, 若是容器對移動操做支持, 則能夠將 std::thread 對象放入其中.
class Func { int i; public: Func(int i_) : i(i_) {} void operator() () { for (unsigned j = 0; j < 10; ++j) { std::cout << i << " "; } std::cout << std::endl; } }; std::vector<std::thread> threads; for (int i = 1; i < 10; i++) { Func f(i); //std::thread t(f); //v.push_back(t); // 不能採用這種方式 //v.push_back(std::move(t)); // 須要使用移動操做才能夠 threads.push_back(std::thread(f)); } std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 對每一個線程調用join()
std::thread::hardware_concurrency() 返回 CPU 核心線程數. 若是沒法查詢系統信息時, 返回0. (static 函數)
get_id() 返回 std::thread 對象關聯的線程的 id. 若是全部權已轉移, 或線程函數已返回, 返回0.
std::this_thread::get_id() 取得當前線程的 id. (static 函數)
一個更好的ThreadGuard
#ifndef _THREAD_GUARD_ #define _THREAD_GUARD_ template <class _Thread> class ThreadGuard { public: explicit ThreadGuard(_Thread& t_) : t(t_) {} ~ThreadGuard() { if (t.joinable()) { t.join(); } } ThreadGuard(const ThreadGuard &) = delete; ThreadGuard& operator=(const ThreadGuard &) = delete; private: _Thread& t; }; #endif // _THREAD_GUARD_