☰
C++ 多线程通信方式全景:从共享内存到结构化协调
2026/10/8 8:25:52 网站建设 项目流程

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. 家族

原语

语义

std::mutex

最基本互斥

std::recursive_mutex

同一线程可重入

std::timed_mutex

支持超时尝试

std::shared_mutex(C++17)

多读单写

std::lock_guard

最简 RAII 锁

std::unique_lock

可延迟/可移动/配 condvar

std::shared_lock

读锁

std::scoped_lock

多锁死锁安全

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. 三个铁律

  1. wait必须有谓词,否则丢唤醒 / 假唤醒都会出错

  2. notify前通常先修改受保护状态

  3. 条件变量不携带数据,只携带“状态可能变了”的信号

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 == 42

2. 能做什么

  • 停止标志: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. 上层封装

工具

作用

std::promise

手动写结果

std::future

读结果

std::shared_future

多消费者读同一结果

std::packaged_task

把“函数调用”绑到 future

std::async

一步完成“起任务 + 给 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::jthread

  • std::stop_token

  • std::stop_source

  • std::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++ 标准线程库”范围。


十二、选型地图

你想做的事

首选

保护多字段状态

mutex+ RAII 锁

等“数据就绪”

condition_variable

简单标志 / 计数

std::atomic

一次性返回结果

future/promise

限制并发数

counting_semaphore

等所有任务完成

latch

每轮迭代汇合

barrier

生产者消费者

线程安全队列

低延迟无锁

atomic + CAS

让线程优雅停

jthread+stop_token

避免共享

thread_local/ 消息传递


十三、核心结论

C++ 多线程通信不是一堆孤立 API,而是一张谱系:

  1. 最底层:原子、内存序、mutex —— 解决可见性与互斥

  2. 事件层:condvar、semaphore、latch、barrier —— 解决“什么时候继续”

  3. 任务层:promise / future / packaged_task / async —— 解决“结果去哪”

  4. 架构层:消息队列、线程池、无锁结构、协程 —— 解决“系统怎么组织”

新手学 mutex,进阶学 condvar,老手写消息传递,架构师尽量少共享。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询