线程(2)

std::thread 的基础用法:

  • 构造函数:
    • std::thread(Function, Args...): 接受一个函数和其参数来创建并启动线程。
    • 拷贝构造被禁用 (Deleted): std::thread t2 = t1; 是非法的,线程对象不能被拷贝。
    • 支持移动构造 (Move Constructor): std::thread t2 = std::move(t1); 是合法的,所有权转移。
  • 参数传递陷阱:
    • 默认按值拷贝: 即使函数参数声明为引用,std::thread 构造函数也会默认拷贝参数。
    • 传引用需用 std::ref 如果想真正传递引用(在线程内修改外部变量),必须使用 std::ref(var) 包装参数。
  • 成员函数调用:
    • 语法:std::thread(&ClassName::MethodName, &object, args...)
    • 必须传入对象指针(或地址)作为第二个参数,以确定在这个对象上调用该方法。
  • 线程管理:
    • join(): 阻塞等待线程结束。
    • detach(): 分离线程,使其在后台运行(需注意生命周期问题)。
    • get_id(): 获取线程 ID。

1. detach() 的“坑”:主线程退出,全员陪葬

现象:运行代码,发现子线程 func5 里的打印语句并没有输出,程序就结束了。 核心原理:

  • 非独立进程: detach() 不会把线程变成一个独立的进程。它只是把线程对象 (std::thread t) 和后台运行的线程执行流“断开联系”。
  • 共存亡: 子线程依然属于当前进程。如果主线程(Main Thread)结束,整个进程(Process)就会销毁,操作系统会强行杀死该进程下所有的线程,无论它们是否被 detach。
  • 资源泄漏风险: 讲师提到如果 detach 的线程还在访问某些资源(如文件、内存),主线程退出导致的强制终止可能导致资源未正确释放。

代码演示:

void func5() {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时
    std::cout << "Thread 5 working..." << std::endl;      // 这句可能来不及打印
}

int main() {
    std::thread t5(func5);
    t5.detach(); // 分离线程,让它在后台跑

    // 主线程立刻结束
    return 0; 
    // 结果:进程销毁,后台的 t5 还没醒过来就被系统杀死了。
}

2. detach 后严禁 join

现象: 尝试先 detach(),然后紧接着调用 join(),结果程序抛出“系统错误”(System Error)并崩溃。 核心原理:

  • std::thread 对象只是一个句柄(Handle)
  • 当你调用 detach() 后,这个句柄就和物理线程断绝关系了。
  • 此时 t5 变成了一个空的壳子(Empty Shell)。你不能对一个空壳子调用 join(),这属于非法操作。

代码演示:

std::thread t5(func5);
t5.detach(); // t5 不再持有线程

if (t5.joinable()) {
    t5.join(); 
} else {
    // detach 后,joinable() 变为 false
    // 再次 join 会 crash
    // t5.join(); // ❌ 崩溃:Invalid Argument
}

3. joinable() 的判断作用

现象: 调用 t5.joinable(),打印结果为 0 (false)。 最佳实践: 在调用 join()detach() 之前,永远建议先检查 joinable()

  • 一个默认构造的线程 std::thread t; 是不可 join 的。
  • 一个已经 join() 过的线程是不可 join 的。
  • 一个已经 detach() 过的线程是不可 join 的。

4. 线程所有权的转移 (std::move)

现象: 演示了 t6_2 = std::move(t6_1),然后尝试操作 t6_1,导致异常。 核心原理:

  • std::thread独占资源(类似 unique_ptr)。一个物理线程只能被一个对象管理。
  • std::move 发生了所有权转移
    • t6_2 拿到了线程的控制权。
    • t6_1 变成了空壳(Null)。
  • 后果: 转移后,原对象 t6_1 就不能再进行任何线程操作(如 join/detach/get_id),否则会报错或行为未定义。

代码演示:

void func() {}

int main() {
    std::thread t1(func);
    
    // t2 接管 t1 的线程,t1 变空
    std::thread t2 = std::move(t1); 

    // t1.join(); // ❌ 错误:t1 已经空了
    t2.join(); // ✅ 正确:t2 现在负责管理线程
}
posted @ 2025-12-20 21:29  belief73  阅读(0)  评论(0)    收藏  举报