C++多线程怎么实现?C++ thread库并发编程详解【进阶实战】

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();  // 等待它结束 —— 必须调用!

⚠️ 关键细节:

  • 线程对象析构前必须处于 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 lock(mtx);  // 构造即加锁,析构即解锁
    ++counter;
}

常见避坑点:

  • 不要手动调用 mtx.lock()/mtx.unlock() —— 异常发生时可能漏解锁
  • 避免嵌套锁或跨作用域持有锁;粒度宜细不宜粗(比如只锁修改部分,不锁整个函数)
  • 竞争激烈时考虑 std::shared_mutex(C++17)支持多读单写

线程间通信:condition_variable 配合 wait/notify

当一个线程需等待另一线程的某个条件成立(如生产者-消费者模型),不能靠轮询,要用条件变量:

std::mutex mtx;
std::queue data_queue;
std::condition_variable cv;
bool ready = false;

// 消费者线程
void consume() {
    std::unique_lock 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 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 工具。写并发代码,保守比激进更可靠。