std::thread 实现多线程需关注生命周期、同步与异常安全,核心是协作而非仅启动;创建后必须 join 或 detach,否则析构时程序终止。

用 std::thread 实现多线程不难,但写对、写稳、写高效需要理解线程生命周期、资源同步和异常安全等关键点。核心不是“怎么启动线程”,而是“怎么让多个线程协作而不打架”。
创建与管理线程:别忘了 join 或 detach
最基础的启动方式是构造 std::thread 对象,传入可调用对象(函数指针、lambda、绑定表达式等):
示例:
void do_work(int id) {
std::cout << "Thread " << id << " running\n";
}
std::thread t(do_work, 42); // 启动线程
t.join(); // 等待它结束 —— 必须调用!
⚠️ 关键细节:
立即学习“C++免费学习笔记(深入)”;
- 线程对象析构前必须处于 joinable 状态,否则程序直接终止(调用
std::terminate) -
join():主线程等待子线程完成;detach():分离后线程后台运行,不再关联对象 —— 但要确保它访问的数据生命周期足够长 - 推荐在 RAII 封装类中自动管理(如
scoped_thread),避免忘记调用
共享数据保护:mutex + lock_guard 是黄金组合
多个线程读写同一变量(如全局计数器、容器)时,必须加锁。裸用 std::mutex 容易出错,配合 std::lock_guard 最安全:
std::mutex mtx;
int counter = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // 构造即加锁,析构即解锁
++counter;
}
常见避坑点:
- 不要手动调用
mtx.lock()/mtx.unlock()—— 异常发生时可能漏解锁 - 避免嵌套锁或跨作用域持有锁;粒度宜细不宜粗(比如只锁修改部分,不锁整个函数)
- 竞争激烈时考虑
std::shared_mutex(C++17)支持多读单写
线程间通信:condition_variable 配合 wait/notify
当一个线程需等待另一线程的某个条件成立(如生产者-消费者模型),不能靠轮询,要用条件变量:
std::mutex mtx;
std::queue<int> data_queue;
std::condition_variable cv;
bool ready = false;
// 消费者线程
void consume() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty() || ready; }); // 原子检查+等待
if (!data_queue.empty()) {
auto val = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << val << "\n";
}
}
// 生产者线程
void produce(int val) {
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(val);
cv.notify_one(); // 唤醒一个等待线程
}
注意:
-
wait()必须配合std::unique_lock(不能用lock_guard) - 谓词 lambda 是必需的 —— 防止虚假唤醒(spurious wakeup)
-
notify_one()和notify_all()的选择取决于业务逻辑是否允许多个线程同时响应
更高级工具:async、promise/future 和 thread_local
不总需要手动管理线程。现代 C++ 提供更高层抽象:
-
std::async:异步执行并返回std::future,适合“发任务拿结果”场景 -
std::promise+std::future:跨线程传递单次值或异常(例如线程内计算完 set_value,主线程 get()) -
thread_local:为每个线程提供独立副本,彻底避免共享 —— 适合缓存、日志上下文、随机数引擎等
例如:
thread_local std::mt19937 rng(std::random_device{}()); // 每线程一个随机数生成器
thread_local std::string thread_id = std::to_string(std::this_thread::get_id());
基本上就这些。多线程不是堆满 std::thread 就完事,关键是理清数据归属、明确同步边界、用对 RAII 工具。写并发代码,保守比激进更可靠。











