- 2.1 线程管理基础
- 2.1.1 启动线程 - 构造 std::thread 对象
- 1.使用线程函数构造
- 2.使用可调用类型构造
- 3.使用 lambda 表达式构造
- 4.明确 std::thread 的运行方式
- 2.1.2 加入式 - 等待线程完成
- 2.1.3 特殊情况下的等待
- 1.避免应用被抛出的异常所终止 - 在异常处理过程中调用 join()
- 2.资源获取即初始化方式(RAII,Resource Acquisition Is Initialization)
- 2.1.4 分离式 - 后台运行线程
标准库头文件 thread
标准库线程类 std::thread
使用可调用对象(函数、类、lambda)构造一个线程对象
1.使用线程函数构造void do_some_work(); std::thread my_thread(do_some_work);2.使用可调用类型构造
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
注意:如果可调用类型是临时变量,而不是一个命名的变量,C++编译器会将其解析为函数声明
std::thread my_thread(background_task());
这里相当与声明了一个名为 my_thread 的函数,这个函数带有一个参数(函数指针指向没有参数并返回 background_task 对象的函数),返回一个 std::thread 对象的函数,而非启动了一个线程
使用多组括号,或使用新统一的初始化语法,可以避免这个问题,如下所示:
std::thread my_thread((background_task())); // 1
std::thread my_thread{background_task()}; // 2
3.使用 lambda 表达式构造
std::thread my_thread([]{
do_something();
do_something_else();
});
4.明确 std::thread 的运行方式
启动了线程,你需要明确是要等待线程结束(加入式),还是让其自主运行(分离式)
加入式和分离式分别需要使用 std::thread 的成员函数 join() 和 detach()
- 注意1:必须在 std::thread 对象销毁之前做出决定,否则你的程序将会终止(std::thread 的析构函数会调用 std::terminate(),这时再去决定会触发相应异常)
- 注意2:保证线程结束之前,可访问的数据的有效性
对象销毁之后再去访问,会产生未定义行为,如下所示:
struct func {
int* i;
func(int* i_) : i(i_) {}
void operator() ()
{
for (unsigned j = 0; j < 1000000; ++j)
{
do_something(*i);
}
}
};
void oops_join()
{
int* some_local_state = new int(0);
func my_func(some_local_state);
std::thread my_thread(my_func);
delete some_local_state;
my_thread.join();
// 新线程试图访问已销毁的对象(some_local_state)
}
这种情况还可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用,下面展示了这样的一种情况:
void oops_detach()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach();
// 不等待线程结束,新线程可能还在运行
}
2.1.2 加入式 - 等待线程完成
如果需要等待线程完成,需要使用 std::thread 的成员函数 join()
加入式可以确保局部变量在线程完成后,才被销毁
只能对一个线程使用一次 join(),一旦已经使用过 join(),std::thread 对象就不能再次加入了,当对其使用 joinable() 时,将返回 false
void f() {
auto my_func = []() {
// do_something();
};
std::thread t(my_func);
try
{
// do_something_in_current_thread();
}
catch (...)
{
t.join();
throw;
}
t.join();
}
2.资源获取即初始化方式(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(); // 如果没有,调用 join() 进行加入
}
// 直接对一个 std::thread 对象进行拷贝或赋值是危险的
thread_guard(thread_guard const&) = delete;
thread_guard& operator=(thread_guard const&) = delete;
};
void f() {
auto my_func = []() {
// do_something();
};
std::thread t(my_func);
thread_guard g(t);
// do_something_in_current_thread();
}
当线程执行完 do_something_in_current_thread() 后,局部对象就要被逆序销毁了
因此,thread_guard 对象 g 是第一个被销毁的,这时线程在析构函数中被加入到原始线程中
如果需要后台运行线程,需要使用 std::thread 的成员函数 detach()
主线程不能与后台运行线程产生直接交互
注意:不能对没有/执行线程/的 std::thread 对象使用 detach()
当 std::thread 对象 t 使用 t.joinable(* 返回的是 true,才可以使用 t.detach()



