C++ 里“线程通信”这件事,容易被讲成“mutex / condvar / atomic 三板斧”。但更准确的说法是:
线程通信 = 在共享地址空间内,借助同步原语完成“数据传递”和“状态通知”。
C++11 之后标准库已经形成一套分层体系:底层是原子与互斥,中间是条件变量与信号量,上层是 future / 任务模型,再往上是消息队列、线程池、协程管道等设计模式。下面按“抽象层级 + 通信语义”系统梳理。
一、共享内存:所有通信的物理基础
同一进程内的线程共享地址空间。因此“线程 A 写、线程 B 读”本身就是通信,只是不安全。
int g = 0; // 线程 A: g = 42; // 线程 B: printf("%d\n", g);没有同步时,这是数据竞争,属于未定义行为。
所以 C++ 多线程通信不是“选一种魔法”,而是:
共享内存负责传输,同步原语负责排序与可见性。
二、互斥与锁:保护“共享状态”的通信
1. 语义
互斥不直接“传消息”,而是保证:
临界区内对共享数据的修改,对其他拿到锁的线程可见
复合不变式不被并发破坏
std::mutex m; int balance = 0; void deposit(int x) { std::lock_guard<std::mutex> lk(m); balance += x; // 对其它加锁线程可见 }2. 家族
原语 | 语义 |
|---|---|
| 最基本互斥 |
| 同一线程可重入 |
| 支持超时尝试 |
| 多读单写 |
| 最简 RAII 锁 |
| 可延迟/可移动/配 condvar |
| 读锁 |
| 多锁死锁安全 |
3. 适用边界
适合:
多字段对象一致性
容器、状态机、配置、缓存
业务不变式复杂,难以用原子表达
不适合:
高频计数器(用 atomic)
单纯“等一件事发生”(用 condvar / semaphore)
三、条件变量:事件通知,而不是数据传输
1. 语义模型
条件变量解决的是:
“某个条件成立时,唤醒在等的线程。”
它必须和 mutex 配合使用,保护的是谓词状态,不是数据本身。
std::mutex m; std::condition_variable cv; std::queue<int> q; void producer() { { std::lock_guard<std::mutex> lk(m); q.push(42); } cv.notify_one(); } void consumer() { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{ return !q.empty(); }); int v = q.front(); q.pop(); }2. 三个铁律
wait必须有谓词,否则丢唤醒 / 假唤醒都会出错notify前通常先修改受保护状态条件变量不携带数据,只携带“状态可能变了”的信号
3. 典型场景
生产者 / 消费者
任务队列
状态机切换
“数据就绪”通知
四、原子变量:轻量级状态与无锁通信
1. 语义
std::atomic<T>提供:
原子性读写
内存序(memory order)
跨线程可见性
std::atomic<bool> ready{false}; int data = 0; // 线程 A data = 42; ready.store(true, std::memory_order_release); // 线程 B while (!ready.load(std::memory_order_acquire)) { // spin / yield } // 此处能看到 data == 422. 能做什么
停止标志:
atomic<bool> stop引用计数
发布指针 / 数据缓冲区
无锁栈、无锁队列、SPSC 通道
3. C++20 的原子等待
std::atomic<int> state{0}; state.wait(0); // 阻塞直到值变化 state.store(1); state.notify_all();这比“mutex + condvar”轻,也比忙等高效。
4. 边界
原子适合:
单变量
标志位
计数器
发布-订阅式指针
不适合:
多字段事务
复杂不变式
“等 N 个条件同时成立”的复杂协调
五、Future / Promise:一次性结果通信
1. 模型
std::promise<int> p; std::future<int> f = p.get_future(); std::thread t([p = std::move(p)] mutable { p.set_value(compute()); }); int r = f.get(); // 阻塞等结果或异常通信内容是:
一个返回值
或一个异常
且只能发生一次
2. 上层封装
工具 | 作用 |
|---|---|
| 手动写结果 |
| 读结果 |
| 多消费者读同一结果 |
| 把“函数调用”绑到 future |
| 一步完成“起任务 + 给 future” |
3. 适用
后台计算返回结果
线程启动结果 / 错误信息回传
一次性请求-响应
不适合:
持续消息流
高频事件
多生产者多消费者管道
六、信号量(C++20):配额与门控
1. 语义
std::counting_semaphore<> sem(3); // 最多 3 个线程进入 void worker() { sem.acquire(); do_work(); sem.release(); }acquire:计数 > 0 则减一,否则阻塞release:计数加一,唤醒等待者std::binary_semaphore是计数上限为 1 的特例
2. 和 mutex 的区别
维度 | mutex | semaphore |
|---|---|---|
所有者 | 有:谁锁谁解 | 无:任何人可 release |
用途 | 保护临界区 | 控制并发数 / 资源配额 |
是否传数据 | 否 | 否 |
3. 适用
连接池
线程数限流
生产者消费者缓冲区槽位
替代某些 condvar + 计数器的手写模型
七、Latch / Barrier(C++20):阶段协调
1.std::latch:一次性倒计时
std::latch lat(N); // N 个线程各自: do_work(); lat.count_down(); // 某线程: lat.wait(); // 等所有人完成适合:
任务拆分后等全部完成
初始化阶段屏障
2.std::barrier:可重用阶段屏障
std::barrier bar(N, []{ // 每轮结束时的汇合动作 }); while (running) { phase_work(); bar.arrive_and_wait(); }适合:
迭代算法
并行 for 的分阶段同步
仿真、渲染、数值计算
八、消息传递:把“共享”变成“传递”
这是工业级并发里最重要的范式转变:
不要让大家抢同一块内存,而是把数据所有权从一个线程移到另一个线程。
1. 线程安全队列
template<class T> class ThreadSafeQueue { std::queue<T> q; std::mutex m; std::condition_variable cv; public: void push(T v) { { std::lock_guard<std::mutex> lk(m); q.push(std::move(v)); } cv.notify_one(); } T pop() { std::unique_lock<std::mutex> lk(m); cv.wait(lk, [this]{ return !q.empty(); }); T v = std::move(q.front()); q.pop(); return v; } };通信语义:
生产者拥有数据 → push → 所有权转移
消费者 pop → 独占数据
没有共享可变状态被并发访问
2. 无锁队列 / SPSC 通道
单生产者单消费者可用 ring buffer + atomic
MPMC 可用 CAS、epoch、hazard pointer
游戏、交易系统、网络栈常用
3. 为什么更高级
死锁面更小
可测试性更好
线程边界清晰
容易扩展到线程池 / actor 模型
九、停止与取消:C++20 的jthread/stop_token
线程通信不只是“传数据”,还包括“让你停下”。
void worker(std::stop_token st) { while (!st.stop_requested()) { do_periodic(); std::this_thread::sleep_for(10ms); } } std::jthread t(worker); // 析构时自动 request_stop() + join()相关原语:
std::jthreadstd::stop_tokenstd::stop_sourcestd::stop_callback
这是“控制面通信”,和“数据面通信”同样重要。
十、线程局部存储:用“不共享”来通信
thread_local int cache = 0;这不是线程间传数据,而是反方向:
通过避免共享,消除同步成本。
常见用法:
每线程缓存
每线程随机数生成器
每线程日志缓冲
后续汇总(reduce)时才通信
十一、高级 / 系统级通信
1. 无锁结构
lock-free stack / queue
RCU
hazard pointer
atomic smart pointer(
std::atomic<std::shared_ptr>C++20)
适合:极低延迟、高争用、内核/交易/网络栈。
2. 协程管道(C++20 起)
task<int> producer(); task<void> consumer() { int x = co_await producer(); }协程本身不是线程,但和线程模型结合后,可以形成:
线程池 + 协程任务
async / await 风格管道
高并发 I/O 通信
3. 共享内存 + 外部 IPC
同一进程内是线程通信;跨进程常借助:
mmap/shm管道 / socket
消息队列
但那些已经超出“C++ 标准线程库”范围。
十二、选型地图
你想做的事 | 首选 |
|---|---|
保护多字段状态 |
|
等“数据就绪” |
|
简单标志 / 计数 |
|
一次性返回结果 |
|
限制并发数 |
|
等所有任务完成 |
|
每轮迭代汇合 |
|
生产者消费者 | 线程安全队列 |
低延迟无锁 | atomic + CAS |
让线程优雅停 |
|
避免共享 |
|
十三、核心结论
C++ 多线程通信不是一堆孤立 API,而是一张谱系:
最底层:原子、内存序、mutex —— 解决可见性与互斥
事件层:condvar、semaphore、latch、barrier —— 解决“什么时候继续”
任务层:promise / future / packaged_task / async —— 解决“结果去哪”
架构层:消息队列、线程池、无锁结构、协程 —— 解决“系统怎么组织”
新手学 mutex,进阶学 condvar,老手写消息传递,架构师尽量少共享。