文章目录
- 线程的基本操作
- 2.1.1 启动线程
- 基本启动方法
- 使用函数对象
- 避免“最令人头痛的语法解析”
- 2.1.2 等待线程完成
- 使用`join()`等待线程结束
- 使用`detach()`分离线程
- RAII方式等待线程完成
- 2.1.3 特殊情况下的等待
- 2.1.4 后台运行线程
- 示例:使用分离线程处理文档
- 总结
线程的基本操作
2.1.1 启动线程
每个C++程序至少包含一个执行main()
函数的主线程。其他线程可以与主线程并行运行,当线程完成其指定任务(即函数执行完毕)时,它们也会自然退出。启动新线程通常通过创建std::thread
对象来实现。
基本启动方法
void do_some_work();
std::thread my_thread(do_some_work);
- 包含头文件:需要包含
<thread>
以使用C++标准库中的多线程功能。 - 构造函数:
std::thread
可以通过函数指针或函数对象进行构造,并在创建时自动启动线程。
使用函数对象
class background_task {
public:void operator()() const {do_something();do_something_else();}
};background_task f;
std::thread my_thread(f);
- 复制机制:函数对象会被复制到新线程的存储空间中,在那里执行和调用。
避免“最令人头痛的语法解析”
传递临时变量而非命名变量可能导致编译器误将其解析为函数声明:
// 错误示例
std::thread my_thread(background_task());// 正确示例
std::thread my_thread((background_task())); // 1
std::thread my_thread{background_task()}; // 2
- Lambda表达式:避免上述问题的有效方法之一是使用Lambda表达式:
std::thread my_thread([]{do_something();do_something_else();
});
2.1.2 等待线程完成
为了确保线程正确结束,可以使用join()
或detach()
方法。
使用join()
等待线程结束
std::thread my_thread(do_some_work);
my_thread.join(); // 主线程等待my_thread完成
- 作用:
join()
使主线程等待直到新线程完成其任务。
使用detach()
分离线程
struct func {int& i;func(int& i_) : i(i_) {}void operator() () {for (unsigned j = 0; j < 1000000; ++j) {do_something(i); // 潜在访问隐患:空引用}}
};void oops() {int some_local_state = 0;func my_func(some_local_state);std::thread my_thread(my_func);my_thread.detach(); // 不等待线程结束
} // 函数返回后,some_local_state被销毁,但新线程可能仍在运行
- 风险:如果线程还在运行而函数已返回,可能会导致对已销毁局部变量的非法访问,造成未定义行为。
RAII方式等待线程完成
为了避免异常情况下线程资源泄露,可以使用RAII模式:
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;
};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();
} // 自动调用thread_guard的析构函数,确保线程正确结束
2.1.3 特殊情况下的等待
在某些情况下,需要更灵活地控制线程的等待,例如超时等待或检查线程是否已经结束。这些需求可以通过条件变量和future等机制实现。
2.1.4 后台运行线程
使用detach()
可以让线程在后台独立运行,脱离与主线程的直接联系:
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
- 守护线程:分离线程通常称为守护线程,适用于长时间运行的任务,如文件系统监控、缓存清理等。
示例:使用分离线程处理文档
void edit_document(std::string const& filename) {open_document_and_display_gui(filename);while (!done_editing()) {user_command cmd = get_user_input();if (cmd.type == open_new_document) {std::string const new_name = get_filename_from_user();std::thread t(edit_document, new_name); // 1t.detach(); // 2} else {process_user_input(cmd);}}
}
- 传参启动线程:不仅可以向
std::thread
构造函数传递函数名,还可以传递函数所需的参数。
总结
通过掌握std::thread
对象的创建、启动、等待和分离等基本操作,可以有效地管理并发任务。使用RAII模式可以确保即使在异常情况下也能正确释放线程资源。对于长时间运行的任务,分离线程提供了一种简便的方式来管理后台任务。