C++多线程为什么不能传引用

背景

在 C++ 里,用 std::thread 启动一个新线程时,你会发现下面这个看似“传引用”的写法并不起作用:

int x = 42;
auto f = [](int& v){
    // do something with v
};
std::thread t(f, x);    // ❌ 这里其实把 x 传给 f 的是一个临时副本,不是引用!

背后主要有两个原因:

1. std::thread 的参数是“Decay-Copy”策略

std::thread 的构造模板大致长这样(简化版):

template <class F, class... Args>
explicit thread(F&& f, Args&&... args);
  • 在内部,它并不是直接把 args... 当作引用来储存
  • 而是对每个 argstd::decay_t<Arg> 的拷贝/移动 ——
    • decay 会把引用类型剥掉,数组转指针,函数转函数指针
    • 最终在线程内部储存的,永远是一个值副本

所以,当你写 thread(f, x) 时,x拷贝 了一份到线程内部,函数实际收到的是这个拷贝,不是原来的 x

2. C++ 设计上要避免“悬垂引用”

如果把一个局部变量的引用塞给线程,线程有可能比调用 std::thread 的作用域活得更久:

void spawn() {
    int x = 100;
    std::thread t(f, std::ref(x));    // f 拿到的是 x 的引用
    t.detach();
}   // x 在这里已经析构,f 里再用 v 就是悬空引用 → 未定义行为!

为了在绝大多数情况下保证安全,标准库选择了:

  • 默认复制所有参数进线程闭包
  • 如果你真要传引用,必须显式地告诉编译器“这是个引用”:
    std::thread t(f, std::ref(x));     // 用 std::reference_wrapper<T>
    
  • 这同时也提醒你:
    1. 线程里的代码在访问同一个对象时,必须自己用 std::mutexstd::atomic 等同步;
    2. 要确保被引用的对象在所有线程都结束前,仍然存活。

正确传引用的示例

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void worker(int& counter) {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    int counter = 0;
    std::thread t1(worker, std::ref(counter));
    std::thread t2(worker, std::ref(counter));
    t1.join();
    t2.join();
    std::cout << "最终 counter = " << counter << "\n";  // 2000
}
  1. std::ref(counter):告诉 std::thread “不要 decay-copy,传 counter 的引用”
  2. std::mutex + lock_guard:避免多个线程并发修改同一个变量导致的数据竞争
  3. 确保 counter 活得够久:直到所有线程 join() 完成

小结

  • 默认std::thread 会把你传给它的每个参数按值复制/移动到内部闭包里。
  • 要传引用:必须用 std::ref()(或 std::cref())显式包装,否则一切都会被“decay”成值;
  • 并发安全:即便拿到引用,也要做好同步(mutexatomic);
  • 生命周期:被引用对象必须在所有线程结束前一直存活,否则就会出现“悬垂引用”与未定义行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值