并发支持库
C11 把并发能力正式纳入标准体系,核心由 <threads.h> 和 <stdatomic.h> 组成:前者提供线程与同步原语,后者提供原子对象和内存序语义。两者分别解决“谁在并发执行”和“并发读写如何保持一致”这两个层面的问题。
1. 本章结构
本章先从 22.1 线程 建立执行单元模型,再进入 22.2 原子操作 讲解无锁读写的语义边界;随后通过 22.3 互斥 与 22.4 条件等待组织协作流程,最后在 22.5 线程局部存储 介绍线程私有状态管理。
2. 并发代码的底线
只要多个线程可能同时访问同一对象,就必须明确同步策略:要么使用原子操作表达并发读写语义,要么通过互斥把访问串行化。未同步的并发冲突会形成数据竞争,而数据竞争在标准层面属于未定义行为。
3. 可移植前提
<stdatomic.h> 在现代实现中的可用性通常较好,<threads.h> 的支持度则更依赖工具链版本。写跨平台代码时,建议先确认目标实现的支持范围,再决定是直接依赖标准线程接口,还是在平台层做统一适配。
4. 原子与互斥的取舍
原子操作适合粒度较小、语义简单的并发状态读写;互斥更适合保护一组需要保持一致性的复合状态。两者没有绝对高下,关键是先把共享对象的一致性约束写清楚,再选择最容易证明正确性的同步方式。
5. 一个共享状态模板
无论选择原子还是互斥,都建议先把“状态对象 + 允许的状态迁移”写成小模板。例如队列可以先定义“空、非空、已关闭”三个状态,再定义每个状态允许的读写动作。这样做的好处是:同步原语只负责保证可见性,真正的业务正确性由状态协议负责,两者职责不会混在一起。
6. 诊断顺序建议
并发故障排查时,先查数据竞争,再查死锁,再查性能瓶颈。因为前两者属于正确性问题,若不先排除,后续性能优化结论通常不可靠。
7. 从共享对象反推同步模型
并发设计最稳妥的起点通常不是“先选原子还是锁”,而是先列出哪些对象会被共享、哪些对象只在单线程内使用。共享对象再映射到同步策略,非共享对象保持无锁。这样能避免为了“统一风格”过度加锁或过度原子化,也更容易在评审中证明正确性。