并发不等于并行
多线程编程首先要分清两个概念。并发指多个任务在重叠的时间段内推进,并行指多个任务在同一时刻真正同时执行。单核 CPU 上多线程只能并发,靠时间片切换制造“同时进行”的错觉;多核环境下才可能并行。理解这一点,才不会把多线程当成单纯的“加速开关”。
线程相比进程更轻量,同一进程内的线程共享地址空间和文件描述符,切换开销小,但共享也意味着风险:一个线程写、另一个线程读,若没有同步约束,结果就依赖执行时序。
线程安全的核心问题
多线程出错,绝大多数源于共享可变状态。典型问题有三类:
- 竞态条件:结果取决于线程调度顺序,如两个线程同时对计数器做“读—加—写”,可能丢失更新。
- 内存可见性:一个线程的写入未必立刻被另一个线程看到,编译器和 CPU 的重排序会加剧这一点,需要内存屏障或原子操作约束。
- 死锁:多个线程互相持有对方需要的锁并等待,形成环路,程序永久卡住。
锁是工具,不是答案
互斥锁能保证临界区的原子性,但代价明确:加锁串行化执行、可能引入优先级反转、粒度不当会拖垮吞吐。实践中可遵循几条原则:
- 缩小临界区:锁内只放必要操作,耗时计算和 I/O 移到锁外。
- 固定加锁顺序:多个锁按全局一致的顺序获取,可消除环路等待,避免死锁。
- 优先用高层抽象:线程安全队列、原子变量、读写锁往往比手写锁更不易错。
- 减少共享:线程私有数据、消息传递、不可变对象,是从根上绕开同步问题的办法。
无锁编程依赖 CAS 等原子指令,吞吐高但正确性难保证,ABA 问题、内存回收都需专门处理,不适合作为默认选择。
设计与调试建议
线程数量并非越多越好,CPU 密集型任务通常取核数附近,I/O 密集型可适当放大。线程池能复用线程、控制并发上限,避免频繁创建销毁。
调试方面,竞态问题往往难以复现,可借助压力测试放大触发概率,用日志记录线程 ID 与关键状态,配合线程检查工具定位数据竞争。代码评审时重点看共享变量的访问路径,比事后排查更划算。
多线程的本质是用复杂度换性能。先问是否真的需要并发,再考虑用哪种同步手段,把正确性放在优化之前,才是稳妥的路径。