每个程序至少有一个线程:执行main()函数的线程,其余线程有其各自的入口函数。线程与原始线程(以main()为入口函数的线程)同时运行。
使用C++线程库启动线程,可以归结为构造 std::thread 对象,其构造函数传入的参数是可调用对象。
void do_some_work(); std::thread my_thread(do_some_work);
需要注意一个关于C++语法解析的问题:“C++’s most vexing parse”
class background_task { public: void operator()() const { do_something(); do_something_else(); } }; background_task f; std::thread my_thread(f);
这里我们构造了一个函数对象f,并传入thread的构造函数中。如果我们直接这么写
std::thread my_thread(background_task());
我们本意是希望使用background_task的构造函数返回一个background对象,并直接用这个临时对象构造一个thread对象。但是在C++中,上面的表达式会被解析为:声明了一个my_thread函数,返回值为thread对象,参数为一个函数指针,这个函数指针指向的函数没有参数,且其返回值background_task对象。
为了避免这种歧义发生,除了提前声明一个函数对象之外,还可以:
std::thread my_thread((background_task()));
std::thread my_thread{background_task()};
thread对象构造完成(线程开始执行)之后,对象析构之前,我们必须选择是等待它(join)或者让它在后台运行(detach),如果你在thread对象析构前没有这么做,那么线程将会终止,因为thread的析构函数中调用了std::terminate()
。
thread对象只能join或detach一次,调用过join或detach的对象再调用joinable将返回false
在使用detach的时候,我们通常在构造完thread对象就立即调用detach了;而join的位置则会选在thread对象析构之前的某个位置,如果在join之前发生了异常,函数将终止,join不会被调用。为了避免这种情况发生,我们可以加上try-catch语句,并且通常来说,我们希望即使发生异常也调用join方法等待。
struct func; void f() { int some_local_state=0; func my_func(some_local_state); std::thread t(my_func); try { do_something_in_current_thread(); } catch(...) { t.join(); throw; } t.join(); }
这种写法过于冗长,我们的目标本质上是希望线程完成之后函数再退出,有一种简洁的方式可以达到这个目的:资源获取就是初始化(RAII,Resource Acquisition Is Initialization)
class thread_guard { std::thread &t; public: explicit thread_guard(std::thread &t_) : t(t_){} ~thread_guard() { if (t.joinable()) { t.join(); } } thread_guard(thread_guard const &) = delete; thread_guard &operator=(thread_guard const &) = delete; }; struct func; void f() { int some_local_state = 0; func my_func(some_local_state); std::thread t(my_func); thread_guard g(t); do_something_in_current_thread(); }
当f函数执行结束时,局部变量析构的顺序与构造顺序相反,即先析构g,再析构t,在thread_gurad的析构函数中调用了join函数,即使do_something_in_current_thread发生异常,join函数也会被调用(实际并不是??)。因为线程只能被join一次,因此要先判断是否joinable。
这里删除了默认的拷贝构造函数和拷贝赋值函数,这是因为拷贝后的对象的生命周期可能会超出thread对象的作用域(例如函数返回一个thread_guard对象)
一旦thread对象调用了detach,线程与thread对象将不再有关联,我们也没有直接的方式与线程通信,也不再能join或detach该线程,此时线程的所有权属于C++运行时库,它保证在线程退出时相关资源被回收。分离的线程通常称为守护进程,它们通常在程序的整个生命周期运行,做一些监控、清理工作。同样的thread对象只能被detach一次