1 每个程序至少有一个线程;
2 启动线程:
1 std::thread t(function); //定义线程对象,传入参数即启动 2 3 如果传入的线程函数是一个类的成员函数,则如下 4 A a;//定义一个A的对象a 5 std::thread t(&A::f,a); //将A类的成员函数地址,和A的对象都传进去
3 仿函数:
一个类中,定义了函数调用操作符(),则类对象叫函数对象,也叫仿函数
1 class A 2 { 3 public: 4 int operate()() //重载函数调用操作符() 5 { 6 return 0; 7 } 8 int operate()(int x, int y) //重载函数调用操作符() 9 { 10 return x+y; 11 } 12 int operate()(int x, int y, int z) //函数调用操作符()支持重载 13 { 14 return x+y+z; 15 } 16 } 17 18 A a; //a即是函数对象,也叫仿函数 19 int ret = a(2,3);
4 线程参数:
线程定义时传入参数给线程的构造函数,既可以传函数名,也可以传仿函数;
std::thread t(hello); //将一个函数名传给线程构造函数
std::thread(a); //将一个无参的函数对象(仿函数)传给线程构造函数
std::thread(a,2,3); //讲一个带参的函数对象,及参数传给线程构造函数
//线程第一个参数必须是函数,所以如果第一个参数是类的对象,则这个类一定定义了函数调用操作符,
//即这个类对象实际上是一个函数对象(仿函数)
5 等待和分离
1 join();//每个线程只能调用一次join,调用以后必须等该线程执行结束才能往下执行。且该线程对象不再是可连接的,joinable()返回false; 2 detch();//分离线程。分离后不必等该线程之行结束,程序即可向后执行。 3 //但detch容易出问题:如果线程中使用了局部变量,函数结束后该线程还在执行,但 4 struct func{ 5 int &i; //结构体定义了一个引用 6 func(int& i_):i(i_){}; //构造函数为引用赋值 7 void operate()() 8 { 9 for(int j=0;j<100000;j++) 10 { 11 do_something(i); 12 } 13 } 14 } 15 void f() 16 { 17 int local_mem = 0; 18 func my_func(local_mem);//仿函数,引用了local_mem 19 std::thread t(my_func); //启动线程 20 t.detch(); 21 } 22 //上述程序f执行中,t线程分离,f运行结束后,t线程可能未结束,但local_mem已经失效,即线程中使用了未定义的变量,会报错
6 explicit
显示定义为不允许隐式类型转换
1 class A 2 { 3 public: 4 int m_x; 5 A(){}; 6 A(int x){ 7 m_x = x; 8 }; 9 10 A( A const &a) = delete; 11 A& operator=(A const &a) = delete; 12 }; 13 int main() 14 { 15 A a; 16 a = 1; //相当于1被隐式转换为了A的类型 17 //隐士转换条件:A的构造函数只有一个参数,或者有多个参数,但除了第一个其余都有默认值 18 //若想避免这种隐式转换, 在构造函数钱加上关键字explicit即可 19 } 20 /////////////////////////// 21 class A 22 { 23 public: 24 int m_x; 25 A(){}; 26 explicit A(int x){ 27 m_x = x; 28 }; 29 }; 30 31 int main() 32 { 33 A a; 34 //a = 1; //加上explicit后,隐式转换会报错 35 a = (A)1; //只能显示转换 36 }
7 传递参数给线程函数
(1)简单示例: void f(int i, std::string const & s); std::thread t( f, 3, "hello"); (2)传参机制 参数会以默认的方式,被复制到内部存储空间。 void f( int i, struct_data& w); void opp() { struct_data data; std::thread t( f, 1, data); t.join(); } //上述opp函数运行后,虽然线程函数f的第二个参数要求是一个结构体数据的引用 //但是,线程对象的构造函数不会在乎线程函数的诉求,所以是直接将data拷贝一份传入 //结果就是,线程t内部存贮空间使用的是data副本的引用,而不是data本身的引用 //所以线程退出后,data的副本随之销毁。对data的修改并没有起作用。 解决方案:在线程构造时,显示的表示传入的是一个引用即可: std::thread t( f, 1, std::ref(data) );
8 转移所有权
1 线程对象不能复制,但可以转移。可以通过 std::move() 转移一个线程的所有权 2 std::thread t( f ); 3 std:: thread t1; 4 t1 = std::move(t); //线程t的所有权将转移到t1 5 //但不能转移到正在执行的线程中 6 std::thread t( f ); 7 std:: thread t1; 8 std:: thread t2; 9 t1 = std::move(t); 10 t1 = std::move(t2);//线程t1正在执行,此时将t2转移到t1,会调用std::terminate()函数终止程序
9 可利用线程数
标准库提供了方法,可以得到程序运行时能够真正并发运行的线程数量指示:
std::thread::hardware_currency();//返回值可能为0,此时你只要替换你想要的线程数量即可。
10 标识线程
线程标识符是 std::thread::id 类型的
每个std::thread::id 具有两个成员 _Hnd 和 _Id;
线程表示符可以用来复制和比较,输出,
如果相等,则说明是同一个线程;
如不相等,则是不同线程,或其中一个是“没有线程”,即没有相关联的执行线程。
线程标识符提供了完整的比较运算符,可以排序。因此,在容器中可作为主键。
行万里路,不忘初心!