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内部已经有一个线程在管理了