C++并发编程--线程的基本管控
发起线程 发起一个空白线程 std::thread t; 将lambda函数作为参数 std::thread t([](){do_something();}); auto f = [](){do_something();}; std::thread t(f); 将类成员函数作为参数 class A{ private: int a = 0; public: void add(){std::cout << a + 1;} }; A a = A(); std::thread t(&A::add, &a); 向线程传入参数 auto f = [](int a, int b){std::cout << a+b;}; std::thread t(f,a,b); 线程一旦启动,就必须明确是等待它结束,还是让其独自运行 如果std::thread对象销毁时未明确方式,std::thread的析构函数会调用std::terminate()终止整个程序 对于可调用函数对象,传入临时变量时编译器可能将其解释为函数声明,如std::thread(f()),可以通过多用一对圆括号,或者是列表初始化方式解决 等待线程结束 std::thread t(do_something); t.join(); //等待线程结束 分离线程 std::thread t(do_something); t.detach(); //不等待线程结束 如果新线程被分离, 该线程可能会使用到已经被销毁的局部变量,处理方法通常为将需要的数据复制到线程内部,而不是共享数据 join()和detach()对一个线程只能调用一次,一个线程一旦选择等待,就不能分离,一个线程,一旦分离,就与主线程分离,不能选择继续汇合,线程的joinable()成员函数可以用于线程是否能够汇合 如果线程启动以后有异常抛出,而join()还未被执行,则join()调用会被略过 如果必须保证新线程先行结束,可以设计一个类,通过标准RAII手法,在析构函数中调用join(),保证主线程在按顺序析构时等待线程的汇合 class thread_guard { private: std::thread t_; public: explicit thread_guard(thread t):t_(std::move(t)){} ~thread_guard(){ if(t_.joinable()) t_.join(); } }; void f(){ int data = 0; auto func = [](int num){do_somthing();}; thread t(f,data); thread_guard g(t); } 试图向线程所执行的函数中传入非const引用会导致编译无法通过,原因是线程不清楚所需执行的函数其参数类型,而是直接复制所提供的值,如果需要按照引用方式传递参数,可以使用std::ref函数对引用进行包装 int num = 0; auto func = [](int& num){do_something();}; // 编译失败,函数func期待一个引用类型,却获取到了一个右值 std::thread t(func,num); // 通过std::ref包装确保传入引用类型 std::thread t(func,std::ref(num)); 如果数据只需要在线程中使用,可以通过std::move向线程转移数据的归属权 int data = 0; auto f = [](int&& data){do_something();}; thread t(f,std::move(data)); 转移线程归属权 std::thread对象负责管控一个执行线程,可以移动,不能复制,可以使用std::move在对象之间转移std::thread对象的归属权 std::thread t1(some_function); std::thread t2(std::move(t1)); 线程被分离或者被汇合之后,则不能继续转移 在运行时选择线程数量 std::thread::hardware_concurrency()用于获取程序中可以真正并发的线程数量, //若信息无法获取,函数返回0 const size_t concurrency_num = std::thread::hardware_concurrency(); const size_t count = concurrency_num ? concurrency_num : 2; 识别线程 通过get_id()获取线程ID std::cout << std::this_thread::get_id();