C++如何实现多线程编程?(std::thread实战指南)

2次阅读

std::Thread构造后必须detach或join,否则析构时调用std::terminate终止进程;传参默认拷贝,引用需std::ref;共享数据必须加锁,即使只读;线程名、优先级等需平台api。

C++如何实现多线程编程?(std::thread实战指南)

std::thread 构造后必须 detach 或 join,否则程序崩溃

构造 std::thread 对象后不处理其生命周期,主线程退出时会调用其析构函数,而默认行为是调用 std::terminate() —— 直接终止整个进程,不会抛异常,也不打印。这是新手最常踩的坑。

  • 必须在对象销毁前显式调用 join()(等待线程结束)或 detach()(分离线程,交由系统托管)
  • join() 只能调用一次;重复调用是未定义行为;调用前需用 joinable() 判断
  • detach() 后不能再调用 join(),也不能再访问该 std::thread 对象的状态
  • 推荐优先用 join(),除非明确需要后台长期运行且不关心完成时机(如日志刷盘线程)

传参到线程函数时,值传递引用传递行为完全不同

std::thread 构造时对参数做**完美转发**,但所有参数都会被拷贝(或移动)进线程私有存储,原始变量不受影响。想传引用?必须套一层 std::ref(),否则你以为的“引用”其实是副本。

  • 直接传 int x → 线程里拿到的是 x 的拷贝,改它不影响主线程的 x
  • std::ref(x) → 线程里操作的是同一块内存,需确保 x 的生命周期长于线程执行时间
  • 指针(如 &x)看似可行,但本质仍是值传递——传的是地址值,仍需手动保证对象不销毁
  • Lambda 捕获同理:[x]() 是值捕获,[&x]() 是引用捕获,后者风险更高
int value = 42; std::thread t([&value]() { value++; }); // 危险!主线程可能已析构 value t.join(); // 若此时 value 已出作用域,UB

线程间共享数据必须加锁,不能靠“我只读不写”侥幸

即使多个线程只读同一个变量,只要该变量可能被其他线程修改,就必须同步。c++ 标准不保证读操作的原子性(除非是 std::atomic 类型),且编译器/CPU 可能重排指令、缓存不一致,导致看到过期值甚至崩溃。

  • 普通 intstd::String 等类型,读写都需保护;不要假设“只读就安全”
  • 首选 std::mutex + std::lock_guard,自动管理锁生命周期
  • 避免手动 lock()/unlock(),容易遗漏或异常跳过
  • 注意死锁:按固定顺序获取多个锁;或用 std::scoped_lock(C++17 起)同时锁多个互斥量

std::thread 不支持线程名、优先级等系统级控制

std::thread 是可移植抽象层,标准没规定如何设名称或调度策略。linux 下可用 pthread_setname_np()windowsSetThreadDescription(),但这些都不是 C++ 标准接口,得自己封装并判断平台。

立即学习C++免费学习笔记(深入)”;

  • 调试时看不到线程名?不是你代码问题,是标准库故意没提供
  • 想调高某线程优先级?必须用平台 API,且需权限(Linux 上通常要 cap_sys_nice
  • 线程局部存储用 thread_local 关键字即可,这个是标准且跨平台的
  • 更复杂的线程池、任务调度需求,建议直接用 std::jthread(C++20)、boost::thread 或第三方库如 folly::Executor

多线程真正的难点从来不在怎么启一个 std::thread,而在于数据怎么流、锁怎么分、生命周期谁管、错误怎么收。写完 t.join() 不代表万事大吉,得盯着每一份共享状态的归属和时效。

text=ZqhQzanResources