t.join()
join
方法用于同步主线程和子线程。也就是说,当主线程调用子线程的 join
方法时,主线程会被阻塞,直到子线程完成其执行。这意味着 join
方法确保主线程等待子线程结束,然后继续执行后续代码。
#include <iostream>
#include <thread>
void threadFunction() {
std::cout << "Thread is running..." << std::endl;
}
int main() {
std::thread t(threadFunction);
// 等待线程t完成
t.join();
std::cout << "Thread has finished." << std::endl;
return 0;
}
在这个示例中:
- 主线程在调用
t.join()
时会被阻塞,直到threadFunction
线程完成。 - 一旦子线程完成,主线程会继续执行并打印 "Thread has finished."。
t.detach()
detach
方法则是让子线程在后台独立运行,与主线程分离。分离后的线程(也称为“游离线程”)不能再被 join
,它的资源会在该线程完成后自动释放。主线程会继续执行而不会等待该子线程。
#include <iostream>
#include <thread>
#include <chrono>
void threadFunction() {
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时任务
std::cout << "Thread is running..." << std::endl;
}
int main() {
std::thread t(threadFunction);
// 分离线程t,使其在后台运行
t.detach();
std::cout << "Main thread is not blocked." << std::endl;
// 主线程继续执行其他工作
std::this_thread::sleep_for(std::chrono::seconds(3)); // 确保主线程不立即退出
return 0;
}
在这个示例中:
- 子线程在后台独立运行,不影响主线程的执行。
- 主线程不会等待子线程完成,而是立即继续执行。
- 分离后的子线程在后台完成其任务并自动释放资源。
如果把std::this_thread::sleep_for(std::chrono::seconds(3)); // 确保主线程不立即退出注释掉:
使用场景
使用 join
- 当你需要主线程等待子线程完成,并且需要确保子线程资源被正确释放时。
- 用于需要顺序执行或线程间必须同步的场景。
使用 detach
- 当你不需要等待子线程完成,并希望子线程在后台独立运行时。
- 适用于不需要同步且子线程任务是“火忘”(fire-and-forget)类型的场景。
内存泄漏和资源泄漏风险
- 未管理的资源:分离的线程继续运行,而主线程不再追踪其状态或结果。如果分离线程未能正确释放其资源,会导致资源泄漏。例如,动态分配的内存、未关闭的文件句柄等。
- 线程生命周期难以管理:一旦线程分离,主线程无法
join
它。这意味着主线程不能直接等待线程完成,必须通过其他方式(如条件变量或信号)来确保线程已完成。否则,主线程可能会在分离的线程完成之前退出,这样会导致未定义行为(如访问已被释放的内存)。 - 程序终止:如果主线程在分离的线程完成之前结束,程序中主线程所占用的资源(如堆栈、静态成员等)将会释放,但分离线程仍然在尝试访问这些资源,可能导致程序崩溃。
解决方法
- 确保分离线程的任务尽快完成:确保分离任务不会长时间运行,以尽量减少与主线程的生命周期管理冲突。
- 使用智能指针:使用智能指针(如
std::shared_ptr
或std::unique_ptr
)来管理动态分配的资源,确保资源在不再需要时能够自动释放。 - 考虑线程池:如果需要频繁启动和销毁线程,使用线程池(
std::thread
pool)可以更高效地管理线程的生命周期,并减少资源管理的麻烦。 - 设置主线程同步:使用某种同步机制(如条件变量、信号等),确保主线程在退出之前等待所有后台线程完成(即使是分离的线程)。