c++并发编程(concurrency)----线程管理
- 启动线程,指定线程运行函数的多种方法
- 等待线程执行完毕
- 唯一辨别线程的方法
如果已经有兴趣启动多线程程序了,那么问自己个问题,如何启动多线程?如何检查多线程执行结束?带着疑问开启我们的多线程之旅。
基础线程管理
每个C++程序都至少有一个线程,由C++运行时库(runtime)启动,线程执行函数main()。当然你的程序也可以启动其他的线程并指定其他的线程入口函数。这些线程可以与初始线程并发运行。类似程序main函数执行完成退出,线程执行函数执行完成返回时线程执行完毕退出。std::thread对象启动一个线程,可以等待它执行完成,第一步先启动线程。
启动线程
- 简单线程执行函数,无参/无返回值
- 带参线程执行函数,可执行独立操作并等待某些消息系统信号
请谨记要包含编译器可看到std::thread 类的定义
跟C++Standard Library 一样,std::thread 可以跟任何(any callable type)可调用类型配合工作,因此你可以传递一个重载调用操作符的函数对象(a class with a function call operator )
class background_task{
public:
void operator() () const{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
完整示例代码
#include <iostream>
#include <thread>
void do_something() {
std::cout << "uniview test ." << std::endl;
}
void do_something_else() {
std::cout << "uniview test else function ." << std::endl;
}
class background_task {
public:
void operator() () const {
do_something();
do_something_else();
}
};
int main()
{
background_task f;
std::thread my_thread(f);
my_thread.join();
}
请注意,std::thread my_thread(background_task());语句不符合语法规则,可验证编译器提示错误,gnu c++编译器报错提示如下:
错误:对成员‘join’的请求出现在‘my_thread’中,而后者具有非类类型‘std::thread(background_task (*)())’
问题原因:One thing to consider when passing a function object to the thread constructor is to avoid what is dubbed “C++’s most vexing parse.”If you pass a temporary rather a named variable, then the syntax can be the same as that of a function declara-tion, in which case the compiler interprets it as such, rather than an object definition.
中文释义:当传递函数对象给线程构造函数时请避免让C++费解的语法。当你传递临时对象而非具名对象时,语法解释同样可理解为是一个函数声明,而非一个对象定义。
解决方法有2个:
- 临时对象外加括号
std::thread my_thread((background_task()));
额外的括号可阻止编译器解释为函数声明,因此允许my_thread被定义为std::thread变量
- 用初始化列表语法
std::thread my_thread{background_task()};
用最新语法{}初始化列表而非(),直接表示定义一个变量无歧义
- 另外一种避免此问题的可调用对象 a lambda expression,它是c++11的新特性
lambda允许写本地函数,可以捕获本地变量,因此替代传递参数的需求
std::thread_my_thread([](
do_something();
do_something_else();
))
线程一旦启动则需要显示决定是否等待线程结束
如果线程启动销毁时没有决定是join()还是detach(),你的程序会异常终止。the std::thread destructor calls std::terminate() 析构函数会调用中止函数。
因此保证线程被正确的join()或者detach()是至关重要的,即使是异常发生时也要正确回收处理。
join()或者detach()一定要在线程销毁前确认完毕,detach()会持续运行很长时间(if you detach it, then the thread may continue running long after the std::thread object is destroyed)。
若不等待线程执行结束,则需要确保线程在执行期间访问数据的有效性,此关注点不是一个新问题,在单线程代码中若要访问一个销毁资源的行为也会出现未定义的行为。不过使用多线程给这种变量生存周期访问的问题更多的机会。
场景举例说明
- 线程执行函数持有局部变量的引用或者指针,局部变量所在函数执行结束但线程运行暂未结束。
代码示例说明 A function that returns while a thread still has access to local variables
struct func
{
int& i;
func(int& i_):i(i_