std::thread
類的構造函數是使用可變參數模板實現的,也就是說,能夠傳遞任意個參數,第一個參數是線程的入口函數,然後面的若干個參數是該函數的參數。ios
第一參數的類型並非c
語言中的函數指針(c
語言傳遞函數都是使用函數指針),在c++11
中,增長了可調用對象(Callable Objects)的概念,總的來講,可調用對象能夠是如下幾種狀況:c++
operator()
運算符的類對象,即仿函數lambda
表達式(匿名函數)std::function
// 普通函數 無參 void function_1() { } // 普通函數 1個參數 void function_2(int i) { } // 普通函數 2個參數 void function_3(int i, std::string m) { } std::thread t1(function_1); std::thread t2(function_2, 1); std::thread t3(function_3, 1, "hello"); t1.join(); t2.join(); t3.join();
實驗的時候還發現一個問題,若是將重載的函數做爲線程的入口函數,會發生編譯錯誤!編譯器搞不清楚是哪一個函數,以下面的代碼:編程
// 普通函數 無參 void function_1() { } // 普通函數 1個參數 void function_1(int i) { } std::thread t1(function_1); t1.join(); // 編譯錯誤 /* C:\Users\Administrator\Documents\untitled\main.cpp:39: error: no matching function for call to 'std::thread::thread(<unresolved overloaded function type>)' std::thread t1(function_1); ^ */
// 仿函數 class Fctor { public: // 具備一個參數 void operator() () { } }; Fctor f; std::thread t1(f); // std::thread t2(Fctor()); // 編譯錯誤 std::thread t3((Fctor())); // ok std::thread t4{Fctor()}; // ok
一個仿函數類生成的對象,使用起來就像一個函數同樣,好比上面的對象f
,當使用f()
時就調用operator()
運算符。因此也可讓它成爲線程類的第一個參數,若是這個仿函數有參數,一樣的能夠寫在線程類的後幾個參數上。併發
而t2
之因此編譯錯誤,是由於編譯器並無將Fctor()
解釋爲一個臨時對象,而是將其解釋爲一個函數聲明,編譯器認爲你聲明瞭一個函數,這個函數不接受參數,同時返回一個Factor
對象。解決辦法就是在Factor()
外包一層小括號()
,或者在調用std::thread
的構造函數時使用{}
,這是c++11
中的新的贊成初始化語法。函數
可是,若是重載的operator()
運算符有參數,就不會發生上面的錯誤。線程
std::thread t1([](){ std::cout << "hello" << std::endl; }); std::thread t2([](std::string m){ std::cout << "hello " << m << std::endl; }, "world");
class A{ public: void func1(){ } void func2(int i){ } void func3(int i, int j){ } }; A a; std::function<void(void)> f1 = std::bind(&A::func1, &a); std::function<void(void)> f2 = std::bind(&A::func2, &a, 1); std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1); std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1); std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2); std::thread t1(f1); std::thread t2(f2); std::thread t3(f3, 1); std::thread t4(f4, 1); std::thread t5(f5, 1, 2);
先提出一個問題:若是線程入口函數的的參數是引用類型,在線程內部修改該變量,主線程的變量會改變嗎?指針
代碼以下:c++11
#include <iostream> #include <thread> #include <string> // 仿函數 class Fctor { public: // 具備一個參數 是引用 void operator() (std::string& msg) { msg = "wolrd"; } }; int main() { Fctor f; std::string m = "hello"; std::thread t1(f, m); t1.join(); std::cout << m << std::endl; return 0; } // vs下: 最終是:"hello" // g++編譯器: 編譯報錯
事實上,該代碼使用g++
編譯會報錯,而使用vs2015
並不會報錯,可是子線程並無成功改變外面的變量m
。code
我是這麼認爲的:std::thread
類,內部也有若干個變量,當使用構造函數建立對象的時候,是將參數先賦值給這些變量,因此這些變量只是個副本,而後在線程啓動並調用線程入口函數時,傳遞的參數只是這些副本,因此內部怎麼操做都是改變副本,而不影響外面的變量。g++
多是比較嚴格,這種寫法可能會致使程序發生嚴重的錯誤,索性禁止了。對象
而若是能夠想真正傳引用,能夠在調用線程類構造函數的時候,用std::ref()
包裝一下。以下面修改後的代碼:
std::thread t1(f, std::ref(m));
而後vs
和g++
均可以成功編譯,並且子線程能夠修改外部變量的值。
固然這樣並很差,多個線程同時修改同一個變量,會發生數據競爭。
同理,構造函數的第一個參數是可調用對象,默認狀況下其實傳遞的仍是一個副本。
#include <iostream> #include <thread> #include <string> class A { public: void f(int x, char c) {} int g(double x) {return 0;} int operator()(int N) {return 0;} }; void foo(int x) {} int main() { A a; std::thread t1(a, 6); // 1. 調用的是 copy_of_a() std::thread t2(std::ref(a), 6); // 2. a() std::thread t3(A(), 6); // 3. 調用的是 臨時對象 temp_a() std::thread t4(&A::f, a, 8, 'w'); // 4. 調用的是 copy_of_a.f() std::thread t5(&A::f, &a, 8, 'w'); //5. 調用的是 a.f() std::thread t6(std::move(a), 6); // 6. 調用的是 a.f(), a不可以再被使用了 t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); return 0; }
對於線程t1
來講,內部調用的線程函數實際上是一個副本,因此若是在函數內部修改了類成員,並不會影響到外面的對象。只有傳遞引用的時候纔會修改。因此在這個時候就必須想清楚,究竟是傳值仍是傳引用!
線程對象之間是不能複製的,只能移動,移動的意思是,將線程的全部權在std::thread
實例間進行轉移。
void some_function(); void some_other_function(); std::thread t1(some_function); // std::thread t2 = t1; // 編譯錯誤 std::thread t2 = std::move(t1); //只能移動 t1內部已經沒有線程了 t1 = std::thread(some_other_function); // 臨時對象賦值 默認就是移動操做 std::thread t3; t3 = std::move(t2); // t2內部已經沒有線程了 t1 = std::move(t3); // 程序將會終止,由於t1內部已經有一個線程在管理了