Launching a thread
Launching a thread
可用作thread 的类型:
- 函数
- 函数指针
- function object: 重载了operator()的类的对象
- lambda
- 类的成员函数
#include <iostream>
#include <thread>
void do_some_work() { std::cout << "simple function\n"; }
class background_task {
public:
void operator() () const { std::cout << "object\n"; }
};
class X{
public:
void do_lengthy_work() { cout << "member function" << endl; }
};
int main()
{
std::thread t0(do_some_work);
background_task f;
std::thread t1(f);
std::thread t2([]() {
std::cout << "lambda\n";
});
X my_x;
std::thread t3(&X::do_lengthy_work,&my_x);
t0.join();
t1.join();
t2.join();
t3.join();
return 0;
}效果:
simple function
object
lambda
member function类对象
[!quote]
the supplied function object is copied into the storage belonging to the newly created thread of execution and invoked from there.
函数对象被拷贝到新线程中执行。
增加拷贝构造。设置默认构造
background_task(const background_task &o) { std::cout << "copy constructor\n"; }
background_task() = default;打印:
copy constructor
object[!quote]
If you pass a temporary rather than a named variable, the syntax can be the same as that of a function declaration, in which case the compiler interprets it as such, rather than an object definition
使用临时类对象作为thread构造参数时,语法形式和函数声明相同,编译器将其解释为函数声明,而不是对象构造。
std::thread my_thread(background_task());IDE提示:
main.cpp:15:18: Parentheses were disambiguated as a function declaration (fix available)修正的方法:"naming your function object"
- 用()包裹function object
std::thread my_thread( (background_task() ) );- 使用列表初始化,表示创建一个对象,参数是temporary object
std::thread my_thread{ background_task() };被解释为函数
my_thread 被解释为函数时的具体含义:
| 部分 | 含义 |
|---|---|
| std::thread | 函数返回类型 |
| my_thread | 函数名称 |
| background_task() | 参数相当于 background_task(*)(): 返回类型:background_task (*) 指针 () 不带参数 |
background_task rbf() {
std::cout << "return function object\n";
return background_task();
}
//typedef background_task(*prbf)();
using prbf = background_task(*)();
//using prbf = background_task(&)();
int main()
{
std::thread my_thread(background_task());
prbf p = rbf;
my_thread(p);
//my_thread(&rbf);
//my_thread(rbf);
return 0;
}
std::thread my_thread(background_task()){
std::cout << "function t\n";
return std::thread();
}| 行号 | 功能 | 说明 |
|---|---|---|
| 1-4 | 定义返回function object,不带参数的函数 | |
| 6-8 | 定义函数指针、引用的别名 | 使用using 定义函数别名时,指针和引用的区别:指针类型定义对象时可以不指定初始化值(具体指向的函数),引用类型定义对象时必须进行初始化。 |
| 12 | 声明my_thread()函数 | |
| 13、14 | 创建函数指针指向rbf()作为my_thread参数 | |
| 16、17 | 直接传递rbf()作为my_thread参数 |
joining or detaching
[!quote]
Once you've started your thread, you need to explicitly decide whether to wait for it to finish or leave it to run its own.
启动线程后明确决定是等待线程执行完毕,还是分离线程,让其独自运行。
在线程对象销毁前决定行为。
子线程访问局部变量的问题
分离的情况下,子线程访问父线程中的局部变量。
实现:
#include <iostream>
#include <thread>
struct func
{
int& i;
func(int& i_):i(i_){}
void operator()()
{
for(unsigned j=0;j<1000000;++j) {
std::cout << "j:" << j << " i:" << (i) << "\n";
}
}
};
void oops()
{
int some_local_state=6;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach();
}
int main()
{
oops();
int i = 0,j = 0;
std::cin >> i;
std::cout << "input i:" << i << '\n';
std::cin >> j;
std::cout << "input j:" << j << '\n';
return 0;
}my_func中的i 引用 局部变量some_local_state,oops()返回后局部变量被销毁。
效果:
j:18128 i:6
j:18129 i:6
j:1
8130 i:5
j:18131 i:5
j:18132 i:5
j:18133 i:5
j:18134 i:5input i:5
j:18135 i:509
j:18136 i:509- 直到输入前,一直打印i:6
即使oops()返回,my_thread引用的局部变量处的值 如果没有被修改,那么它仍然打印原值6 - 输入input i:5后,my_thread打印值发生变化
waiting for a thread to complete
- 调用方式:by calling join() on the associated std::thread instance.
- 在调用点等待线程执行完毕,然后才能继续执行。
- join 的方式很死板,如果子线程一直运行,那么就会永远的等待。更灵活的方式:有条件的等,条件可以是到达一定时间 或 接收消息。。。
- join()会清理关联的thread,
- 通过joinable()判断thread 是否能够join,对于detach 的thread 和 已经join 的thread 它们不可join
joinable 测试:
auto f = []() {};
std::thread t0(f);
t0.detach();
std::cout << "detach thread joinable: " << t0.joinable() << std::endl;
std::thread t1(f);
std::cout << "before call join joinable: " << t1.joinable() << std::endl;
t1.join();
std::cout << "after call join joinable: " << t1.joinable() << std::endl;效果:
detach thread joinable: 0
before call join joinable: 1
after call join joinable: 0Waiting in exceptional circumstances
如果创建线程后不是立即join 线程,而是执行其他操作,然后再join。那么在join 前可能抛出异常,导致join()被跳过。
void f()
{
int i = 3;
func my_func = {i};
std::thread t(my_func);
auto dosomething = []() { throw std::exception(); };
dosomething();
std::cout << "join\n";
t.join();
}循环次数10
效果:
。。。
j:8 i:3
j:9 i:3
terminate called after throwing an instance of 'std::exception'
what(): std::exception子线程还是执行完毕了
正常退出时打印的内容为:Process exited with code: 0.
这里main 中抛出exception,程序terminal
没有打印 join,thread 没有join
问:修改子线程中的循环次数,10000,100000 都能执行完,为什么?主线程已经抛出异常了,子线程为什么没有被销毁?
答:这和编译器有关,上面的输出是使用Mingw 进行编译的结果。使用msvc编译器编译直接exited with code 3. msvc的结果符合期望-抛出异常-> 没有捕获,向上传递 -> 程序终止 -> 线程销毁,不会执行。
为了保证要join 的thread 能被join,可以:
- 捕获异常,在异常处理中进行join;
- 通过RAII,thread 作为类属性,在类的析构中调用join
捕获异常
实现:
void f()
{
int i = 3;
func my_func = {i};
std::thread t(my_func);
try {
auto dosomething = []() { throw std::exception(); };
dosomething();
}
catch(...)
{
std::cout << "catch exception join\n";
t.join();
throw;
}
std::cout << "join\n";
t.join();
}
int main()
{
f();
return 0;
}| 行号 | 功能 | |
|---|---|---|
| 6-8 | 执行可能出现异常的代码 | |
| 10 | 捕获所有异常 | |
| 11-15 | 异常处理 | 调用join |
| 14 | 重新抛出给上一层调用 | |
| 12、13 | 没有异常时,正常join |
效果:
catch exception join
j:0 i:3
j:1 i:3
j:2 i:3注意:如果使用msvc 编译器进行编译,会弹出提示窗口:
点击忽略按钮,Application Output 窗口才会显示上面的输出信息,否则直接退出没有打印内容。
RAII处理异常
实现:
class thread_guard {
std::thread &t;
public:
explicit thread_guard(std::thread &t_) : t(t_) { std::cout << __func__ << std::endl; }
~thread_guard() {
std::cout << __func__ << std::endl;
if(t.joinable())
{
std::cout << "join" << std::endl;
t.join();
}
else
std::cout << "cann't be joined\n" << std::endl;
}
thread_guard(thread_guard const &) =delete;
thread_guard& operator= (thread_guard const &) =delete;
};
void f()
{
int i = 3;
func my_func = {i};
std::thread t(my_func);
thread_guard g(t);
auto dosomething = []() { throw std::exception(); };
dosomething();
}- 函数栈销毁时local objects 按照和构造顺序相反的顺序销毁;
- 抛出异常时,会销毁函数栈;
- thread_guard 析构时会join其关联的thread ;
- 构造thread_guard 对象时要求给定关联thread;
- 禁止拷贝、赋值,避免在thread_guard 对象所在生存区外使用 关联的thread
如果可以拷贝或赋值
thread_guard g0(t);
{
thread_guard g1 = g0;
} //销毁g1
//此时t已经join,不能再使用t(包括对其进行join)效果:
thread_guard
~thread_guard
join
j:0 i:3
j:1 i:3
j:2 i:3
catch exceptionRunning threads in the background
[!quote]
Detached threads are often called daemon threads after the UNIX concept of a daemon process that runs in the background without any explicit user interface.
daemon thread: 守护线程
使用:
| 序号 | 示例 | 说明 |
|---|---|---|
| 1 | monitoring the filesystem | 例如,监控装置,存储空间有限,需要定期清理数据; |
| 2 | clearing unused entries out of object caches | 堆内存储的对象存储到文件中 |
| 3 | optimizing data structures | 区分功能; |
| 4 | 同时运行多个数据参数互相独立但是功能相同的函数 |