C++创建线程的几种方法详解
作者:Lzg_na
1. 引言
从 C++11 开始,标准库正式引入了线程支持,让开发者无需依赖平台特定的 API(如 Windows 的 CreateThread 或 POSIX 的 pthread_create)即可编写跨平台的多线程代码。
在众多线程创建方式中,std::thread、std::async 和 std::jthread 是最常用的三种。它们各有特点,适用于不同的场景。本文将逐一介绍它们的用法、差异以及选择时机,帮助你在实际项目中做出合适的选择。
2. std::thread:最基础的线程类
2.1 基本用法
构造函数原型
// C++11 起 template<class Function, class... Args> explicit thread(Function&& f, Args&&... args); // 移动构造(C++11 起) thread(thread&& other) noexcept;
f:要在线程中执行的可调用对象(函数指针、函数对象、Lambda 等)。args...:传递给f的参数,按值传递到新线程。- 移动构造允许将线程所有权转移给另一个
thread对象。
std::thread 是 C++11 引入的线程类,它直接创建一个操作系统线程,并在该线程上执行指定的可调用对象(函数、函数对象、Lambda 表达式等)。
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread, id = " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t(hello); // 创建线程并启动
t.join(); // 等待线程结束
return 0;
}
也可以传递参数:
#include <iostream>
#include <thread>
void print_sum(int a, int b) {
std::cout << "Sum = " << (a + b) << std::endl;
}
int main() {
std::thread t(print_sum, 3, 5);
t.join();
return 0;
}
2.2 关键注意事项
- 必须调用
join()或detach():std::thread对象析构时,如果线程仍处于 joinable 状态,程序会调用std::terminate直接终止。因此,务必在合适的时机调用join()(等待线程结束)或detach()(分离线程,让其后台运行)。 - 不可复制,只能移动:
std::thread是 move-only 类型,不能拷贝,但可以通过std::move转移所有权。 - 异常安全:如果线程函数抛出异常且未被捕获,程序会终止。建议在线程函数内部捕获所有异常。
#include <iostream>
#include <thread>
int main() {
std::thread t([] {
try {
// 可能抛出异常的代码
throw std::runtime_error("error");
} catch (const std::exception& e) {
std::cerr << "Caught: " << e.what() << std::endl;
}
});
t.join();
return 0;
}
2.3 适用场景
- 需要完全控制线程生命周期(何时等待、何时分离)。
- 执行耗时任务,且不需要直接获取返回值。
- 需要精细管理线程数量、优先级等底层细节。
3. std::async:异步任务与返回值
3.1 基本用法
函数原型
// C++11 起 template<class F, class... Args> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(F&& f, Args&&... args); // C++11 起 template<class F, class... Args> future<invoke_result_t<decay_t<F>, decay_t<Args>...>> async(launch policy, F&& f, Args&&... args);
policy:启动策略,取std::launch::async或std::launch::deferred(可组合)。f:要异步执行的可调用对象。args...:传递给f的参数。- 返回
std::future,用于获取结果或异常。
std::async 是 C++11 引入的异步任务工具。它接受一个可调用对象,返回一个 std::future 对象,通过 future.get() 可以获取任务的返回值或异常。
#include <iostream>
#include <future>
int compute(int x) {
return x * x;
}
int main() {
std::future<int> fut = std::async(std::launch::async, compute, 7);
int result = fut.get(); // 阻塞直到任务完成,获取返回值
std::cout << "Result = " << result << std::endl;
return 0;
}
3.2 启动策略
std::async 支持两种启动策略:
| 策略 | 含义 |
|---|---|
std::launch::async | 强制在新线程上异步执行 |
std::launch::deferred | 延迟执行,在调用 get() 或 wait() 时才同步执行 |
std::launch::async | std::launch::deferred | 默认策略,由标准库实现决定 |
// 强制异步执行 auto fut1 = std::async(std::launch::async, task); // 延迟执行(惰性求值) auto fut2 = std::async(std::launch::deferred, task); // 默认策略(实现决定) auto fut3 = std::async(task);
3.3 关键注意事项
- 默认策略的不确定性:使用默认策略时,无法确定任务是否真的在新线程上执行。如果依赖线程并发,应显式指定
std::launch::async。 future析构会阻塞:当std::future析构时,如果任务尚未完成且采用std::launch::async策略,析构函数会阻塞等待任务完成。这在某些场景下可能造成意外阻塞。- 适合获取返回值:相比于
std::thread,std::async更方便地获取任务结果和异常。
3.4 适用场景
- 需要获取异步任务的返回值或捕获异常。
- 任务相对独立,不需要精细控制线程生命周期。
- 希望代码简洁,避免手动管理
std::thread和共享状态。
4. std::jthread:自动 join 的现代线程
4.1 基本用法
构造函数原型
// C++20 起 template<class Function, class... Args> explicit jthread(Function&& f, Args&&... args); // C++20 起:接受停止令牌版本 template<class Function, class... Args> explicit jthread(Function&& f, Args&&... args); // 移动构造(C++20 起) jthread(jthread&& other) noexcept; // 拷贝构造被删除(不可复制) jthread(const jthread&) = delete;
f:要在线程中执行的可调用对象。args...:传递给f的参数。- 若
f的第一个参数类型为std::stop_token,则自动传入内部停止令牌,供协作式取消使用。 - 析构时自动调用
join(),无需手动管理。
std::jthread 是 C++20 引入的线程类,全称是 “joining thread”。它的核心优势是析构时自动调用 join(),避免了忘记 join() 导致程序终止的问题。
#include <iostream>
#include <thread>
int main() {
std::jthread t([] {
std::cout << "Hello from jthread" << std::endl;
});
// 无需手动调用 join(),析构时自动 join
return 0;
}
4.2 协作式取消
std::jthread 还引入了**停止令牌(stop token)**机制,支持协作式取消。通过 std::stop_token,可以在外部请求线程停止,线程内部可以响应这个请求。
#include <iostream>
#include <thread>
#include <chrono>
int main() {
std::jthread t([](std::stop_token st) {
while (!st.stop_requested()) {
std::cout << "Working..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << "Stopped." << std::endl;
});
std::this_thread::sleep_for(std::chrono::seconds(1));
t.request_stop(); // 请求停止
return 0; // 析构时自动 join
}
4.3 关键注意事项
- 需要 C++20 支持:
std::jthread是 C++20 标准的一部分,需要编译器支持 C++20。 - 自动 join 的代价:析构时自动
join()意味着如果线程还在运行,析构会阻塞等待。如果希望线程在后台运行,仍需显式detach()(但jthread的detach()会失去停止令牌能力)。 - 停止令牌是协作式的:
request_stop()只是发出请求,线程内部必须检查stop_requested()才能实际停止。如果线程在阻塞调用中(如sleep_for),需要配合条件变量或wait接口才能及时响应。
4.4 适用场景
- 希望避免忘记
join()导致的程序崩溃。 - 需要协作式取消机制,优雅地停止后台任务。
- 使用 C++20 及以上的项目。
5. 三种方式的对比
| 特性 | std::thread | std::async | std::jthread |
|---|---|---|---|
| 引入版本 | C++11 | C++11 | C++20 |
| 获取返回值 | 不支持(需手动共享状态) | 支持(通过 future) | 不支持(需手动共享状态) |
| 自动 join | 否(需手动调用) | 不适用 | 是(析构时自动) |
| 协作式取消 | 不支持 | 不支持 | 支持(stop_token) |
| 生命周期控制 | 完全手动 | 由 future 管理 | 自动 join |
| 异常传递 | 线程内需自行处理 | 通过 future 传递 | 线程内需自行处理 |
| 适用场景 | 精细控制线程 | 异步任务 + 返回值 | 安全便捷的后台任务 |
6. 选择时机建议
6.1 优先选择 std::jthread(C++20 项目)
如果你的项目使用 C++20,且不需要获取返回值,优先使用 std::jthread。它自动 join() 的特性极大降低了因忘记 join() 导致程序崩溃的风险,内置的停止令牌也让线程的优雅退出变得简单。
std::jthread worker([](std::stop_token st) {
while (!st.stop_requested()) {
// 处理任务
}
});
6.2 需要返回值时选择 std::async
如果任务需要返回结果,或者希望异常能安全地传递到主线程,std::async 是最简洁的选择。它把线程管理和结果获取封装在一起,代码更清晰。
std::future<int> fut = std::async(std::launch::async, [] {
return heavy_computation();
});
int result = fut.get();
6.3 需要精细控制时选择 std::thread
当需要精确控制线程的创建时机、生命周期、分离策略,或者需要管理线程池、设置线程优先级等底层操作时,std::thread 仍然是最灵活的选择。但务必记得调用 join() 或 detach()。
std::thread t(worker); // 精细控制:决定何时 join 或 detach t.join();
7. 总结
std::thread是最基础的线程类,灵活但需要手动管理生命周期。std::async适合需要获取返回值或传递异常的异步任务。std::jthread是 C++20 的现代选择,自动join()并支持协作式取消。
在实际开发中,优先考虑 std::jthread(C++20 项目)或 std::async(需要返回值),只有在需要精细控制时才使用 std::thread。合理选择线程创建方式,能让代码更安全、更简洁、更易维护。
以上就是C++创建线程的几种方法详解的详细内容,更多关于C++线程创建方法的资料请关注脚本之家其它相关文章!
