并发基础概念

并发编程要解决的是:让多个任务在时间上重叠推进,同时仍能得到可预测、可维护的结果。它不等于“创建很多线程”;共享状态的所有权、同步方式和退出路径才是正确性的核心。

并发、并行与异步

概念 关注点 是否需要多核 例子
并发(concurrency) 多个任务在同一时间段内交替推进 不需要 单核上交替处理网络连接与日志写入
并行(parallelism) 多个任务在同一时刻真正执行 通常需要 多核同时计算不同数据块
异步(asynchrony) 发起操作后先做别的事,结果稍后取得 不需要 提交任务后通过 std::future 取得结果

异步是程序组织方式,并发和并行是任务的执行现象。一个异步任务可能延迟到调用 get() 时才在当前线程执行;一个多线程程序也可能因为锁竞争而几乎没有并行收益。详见异步机制。

1
2
3
单核并发:  AAAAA  BBBBB  AAAAA
多核并行: 核心 1:AAAAA
核心 2:BBBBB

进程与线程

进程通常是操作系统进行隔离和资源管理的单位;线程是进程内的执行单元。具体实现随操作系统而异,下面是 POSIX/Linux 等常见模型。

资源或状态 同一进程的线程是否共享
虚拟地址空间、堆、全局/静态对象 共享
打开的文件描述符 通常共享同一描述表语义
每个线程的栈、寄存器上下文、程序计数器 不共享
thread_local 对象 每线程一份

线程共享内存使通信成本低,也意味着一个线程写入的数据会影响其他线程。跨进程通信则需要管道、Unix 域套接字、共享内存等 IPC;共享内存同样需要同步。

为什么使用并发

  • I/O 密集型程序可在一个任务等待网络或磁盘时推进其他任务。
  • 独立的 CPU 密集型计算可在多核上并行。
  • 交互程序可将耗时工作移出响应线程。

但线程不是免费的:创建、调度、缓存失效、同步与调试都有成本。CPU 密集型任务的可运行线程通常不应远多于可用核心;I/O 任务也应设置队列、连接或任务数量上限。

共享状态、竞态条件与数据竞争

**竞态条件(race condition)**指结果依赖线程交错顺序。例如“先检查队列非空,再取元素”若不作为一个受保护的整体,就可能出错。

数据竞争(data race)是 C++ 内存模型中的严格术语:两个线程并发访问同一内存位置,至少一个是写入,且这些访问不是原子操作、也没有建立 happens-before 关系。数据竞争会导致未定义行为,不只是“偶尔计数不准”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <thread>

int counter = 0; // 非原子共享对象

void add() {
for (int i = 0; i < 100'000; ++i) {
++counter; // 与另一线程并发执行时发生数据竞争:未定义行为
}
}

int main() {
std::thread first(add);
std::thread second(add);
first.join();
second.join();
}

修复方式取决于不变量:单个计数器可使用 std::atomic<int>;需要让“检查、修改、多对象更新”保持整体一致时,使用互斥锁。详见同步机制。

happens-before:可见性与顺序的依据

“一个线程已经写过”不自动意味着另一个线程能按预期看到它。C++ 用 happens-before(先行发生) 描述跨线程可见性和顺序关系。常见建立方式包括:

  • 同一 std::mutex 的解锁与随后成功加锁;
  • 对同一原子对象的 release 写入与读到该值的 acquire 读取;
  • 线程函数完成与另一个线程成功 join() 返回;
  • 条件变量等待方被唤醒后重新获得用于保护条件的互斥锁。

原子性只保证一次原子读写不被拆开;它不自动维护多个变量之间的一致性。内存序和 CAS 的边界见CAS。

常见问题

问题 含义 常见处理方向
死锁 线程循环等待彼此持有的资源 固定加锁顺序,或使用 std::scoped_lock
活锁 线程都在运行、反复让步,却没有推进工作 限制重试,加入退避,重新设计协议
饥饿 某任务长期得不到执行或资源 降低不公平竞争,限制队列与优先级
伪共享 不同线程频繁写同一缓存行上的不同变量 调整数据布局;先用性能分析确认

实践建议

  • 优先减少共享可变状态:消息传递、不可变数据和明确所有权通常比“到处加锁”更易维护。
  • 把临界区做小,但不要为了缩短锁范围而把本应原子完成的操作拆开。
  • 先定义停止、异常和资源生命周期,再启动线程。
  • 使用 -fsanitize=thread(ThreadSanitizer,平台支持时)排查数据竞争;它不能证明程序完全正确。