创建并启动 std::thread 对象需传入可调用对象(如函数指针、lambda),构造即启动;必须显式调用 join() 或 detach(),否则析构时触发 std::terminate()。

怎么创建和启动一个 thread 对象
用 std::thread 启动线程,最直接的方式是传入可调用对象(函数指针、lambda、绑定对象等),构造即启动,不需显式调用“start”——C++ 里没有这个方法。
常见错误是忘记让主线程等待子线程结束,导致 std::thread 对象析构时调用 std::terminate():
void task() { std::cout << "hello from thread\n"; }
int main() {
std::thread t(task);
// ❌ 错误:t 析构前未 join/detach,程序崩溃
}
- 必须在
std::thread对象销毁前调用join()(阻塞等待完成)或detach()(分离,后台运行) - lambda 捕获局部变量要小心:值捕获安全,引用捕获可能悬空(尤其在线程生命周期长于局部作用域时)
- 传参给线程函数时,
std::thread会**拷贝参数**,不会自动转发引用;如需传引用,必须包装成std::ref(x)
如何向线程函数传递参数(尤其是引用和移动对象)
默认按值传递,这是为了线程安全——避免多个线程同时访问同一栈变量。但有时你确实需要传引用或转移资源所有权。
例如,想让线程修改某个外部变量:
立即学习“C++免费学习笔记(深入)”;
int value = 42;
std::thread t([](int& v) { v *= 2; }, std::ref(value));
t.join(); // value 现在是 84
- 传左值引用:用
std::ref(var),不能直接写&var(会被当作函数签名的一部分,编译失败) - 传右值或移动语义:用
std::move(obj),比如把一个std::vector移入线程处理 - 如果参数是临时对象(如
std::string("hello")),它会被拷贝进线程私有栈,无需额外处理
thread 对象的生命周期管理有哪些坑
std::thread 是可移动、不可复制的类型,这意味着你不能把它放进容器(如 std::vector<:thread></:thread>)而不做额外处理——但其实可以,只要用 std::move 赋值。
典型误用场景:
- 把
std::thread放进容器后,忘记对每个元素调用join()或detach(),析构时崩溃 - 在异常路径中遗漏
join(),比如某次循环中途抛异常,后续线程没被清理 - 重复调用
join()或对已detach()的线程再join(),触发std::system_error
更稳健的做法是封装一个 RAII 类型(比如 scoped_thread),或用 std::unique_ptr<:thread></:thread> 配合 joinable() 检查。
join 和 detach 到底该选哪个
选 join() 表示你关心线程结果、需要同步、或者资源依赖其完成;选 detach() 表示你只管“发出去”,不再关心它何时结束、是否成功。
-
detach()后线程变成守护线程(类似 POSIX 的 daemon thread),其资源由系统回收,但你无法再获取返回值、也无法捕获它的异常(异常会直接终止整个程序) - 调试时几乎总是优先用
join(),否则出错时难以定位是哪个 detached 线程崩了 - 服务器程序中偶尔用
detach()处理短生命周期后台任务(如日志落盘),但务必确保它不访问任何可能提前释放的资源(如局部对象、this 指针)
真正容易被忽略的是:std::thread 的移动操作不会自动帮你 join() 或 detach(),移动后的原对象变为不可连接状态(joinable() == false),但你仍需对目标对象负责。











