在 C++ 中,并发(Concurrency) 和 并行(Parallelism) 是提高程序性能(尤其是利用多核处理器)的核心概念
### 1.并发:
- 概念: 指系统**看起来**能同时处理多个任务。即使只有一个 CPU 核心,操作系统也可以**通过快速切换(时间片轮转)执行多个线程**,给用户一种“同时”进行的错觉;焦点在于任务的结构、分解、管理、响应性(避免阻塞)。
- c++提供的工具:
- std::thread: 创建和管理线程的基本单元。
- std::mutex, std::lock_guard, std::unique_lock: 互斥锁,保护共享数据免受并发访问导致的数据竞争。
- std::condition_variable: 允许线程等待特定条件成立或通知其他线程条件已改变,用于线程间同步。
- std::future, std::promise, std::async: 用于异步操作和获取异步操作结果。
- std::atomic: 提供对基本数据类型(int, bool, pointer 等)的无锁原子操作,避免简单场景下的互斥锁开销。
### 2.并行
- 概念: 指系统**真正同时执行**多个任务。这需要多个 CPU 核心或处理器。多个任务在同一物理时刻都在执行。利用硬件资源(多核)加速计算密集型任务。
- C++提供的工具:
- std::thread: 可以创建多个线程,操作系统可以将其调度到不同核心上真正并行执行。
- 并行算法库(C++17 起): 标准库中许多算法(如 std::sort, std::for_each, std::transform, std::reduce)增加了接受执行策略的重载:
- std::execution::seq: 顺序执行(非并行)。
- std::execution::par: 允许并行执行(可能在不同线程)。
- std::execution::par_unseq: 允许并行和向量化(SIMD)执行(约束更多)。
- std::async (配合 std::launch::async 策略): 可以启动异步任务,这些任务可能会在新线程中并行执行。
### 3.关键区别与联系:
- 并发是概念,并行是实现: 并发是一种编程模型或问题分解方式;并行是实现并发的一种具体手段(通过多核硬件)。
- 并行是并发的子集: 所有并行都是并发的(因为多个任务同时在推进),但并非所有并发都是并行的(单核上的多线程是并发但不是并行)。
- 目标不同: 并发主要关注任务管理和响应性;并行主要关注计算加速。
- 依赖硬件: 并行需要多核/多处理器硬件支持才能真正实现;并发可以在单核上有效工作
### 4.C++ 中实现并发/并行的核心挑战:
- 数据竞争: 当多个线程同时读写同一共享数据且至少有一个是写操作时发生。导致未定义行为(程序崩溃、错误结果等)。
解决方案: 互斥锁 (std::mutex)、原子操作 (std::atomic)、避免共享数据(线程局部存储 thread_local)、只读共享数据。
- 死锁: 两个或多个线程相互等待对方持有的资源,导致所有线程永久阻塞。
解决方案: 锁顺序一致、使用 std::lock 或 std::scoped_lock 一次性锁定多个互斥量、避免嵌套锁、设置超时。
- 活锁: 线程不断响应其他线程的动作(如尝试避开死锁而重复执行相同的回退操作),但无法取得实际进展。
解决方案: 引入随机性、重新设计协调逻辑。
- 假共享: 多个线程频繁访问同一缓存行中不同但相邻的变量,导致缓存行在 CPU 核心间无效化,显著降低性能(尽管没有数据竞争)。
解决方案: 内存对齐 (alignas)、填充字节、将频繁被不同线程访问的变量分开。