并发和多线程
并发和多线程对于构建高性能和响应式应用程序至关重要,尤其是在 C++ 中,可以直接控制线程。虽然 JavaScript 主要为单线程(异步操作由事件循环处理),但 C++ 提供了强大的机制来实现真正的并行,允许同时运行多个任务。
多线程基本概念
- 进程: 具有自己内存空间的独立执行单元。
- 线程: 进程内部的轻量级执行单元。同一进程内的线程共享相同的内存空间,这允许高效的数据共享,但也引入了竞争条件等挑战。
- 并发: 能够同时执行多个任务(例如,通过在单一核心上快速切换任务)。
- 并行: 能够真正同时执行多个任务(例如,通过在不同的 CPU 核心上运行任务)。
- 竞争条件: 多个线程同时访问和修改共享数据,最终结果取决于其执行不可预测的时序的情况。
- 死锁: 两个或多个线程无限期地阻塞,等待彼此释放资源的情况。
std::thread
用法
C++11 引入了 std::thread
用于创建和管理线程。它提供了一种简单且可移植的方式来启动新的执行流程。
正在加载...
互斥锁和条件变量
当多个线程共享数据时,需要机制来防止竞争条件并确保数据完整性。
互斥锁 (std::mutex
)
- 互斥锁 (mutual exclusion) 是一种同步原语,用于保护共享数据免受多个线程的并发访问。一次只能有一个线程获取互斥锁。
lock()
: 获取互斥锁。如果已锁定,调用线程将阻塞。unlock()
: 释放互斥锁。std::lock_guard
/std::unique_lock
: 互斥锁的 RAII 包装器,确保它们在超出作用域时自动解锁,即使发生异常。
正在加载...
条件变量 (std::condition_variable
)
- 条件变量用于根据特定条件同步线程。它们允许线程等待直到条件变为真,并在条件改变时收到通知。
- 通常与互斥锁一起使用,以保护条件所依赖的共享数据。
原子操作 (std::atomic
)
原子操作是保证完全且不可分割地执行的操作,即使在多线程存在的情况下也是如此。它们对于简单、单变量更新很有用,在这种情况下,互斥锁可能过于繁重。
std::atomic<int>
:为整数提供原子操作。fetch_add
、compare_exchange_weak
等操作是原子的。
正在加载...
异步编程 (async/await
)
虽然 C++ 具有传统的多线程,但现代 C++(C++11 onwards)也提供了促进异步编程的功能,类似于 JavaScript 的 async/await
。
std::future
和std::promise
: 用于从异步操作获取结果。std::async
: 启动异步任务并返回一个std::future
,该std::future
最终将保存结果。- 协程 (C++20): 用于编写看起来同步的异步代码的更高级功能。
正在加载...
线程池设计
线程池是预先初始化线程的集合,可用于执行任务。任务不是为每个任务创建一个新线程,而是提交到线程池,由可用的线程拾取并执行任务。这减少了线程创建和销毁的开销,提高了具有许多短生命周期任务的应用程序的性能。
优点:
- 减少线程创建/销毁的开销。
- 管理活动线程的数量,防止资源耗尽。
- 提高响应性。
与 JavaScript 异步编程的比较
JavaScript 的并发模型基于单线程事件循环。虽然它可以处理许多操作并发(例如,网络请求、计时器)而不会阻塞主线程,但它通过异步回调、Promise 和 async/await
实现了这一点,而不是真正的并行。
特性 | JavaScript (事件循环、Async/Await) | C++ (多线程、Async/Future) |
---|---|---|
并发 | 通过非阻塞 I/O 和事件循环实现 | 通过多线程实现真正的并行 |
共享内存 | 有限 (Web Workers 通过消息传递,SharedArrayBuffer 通过 Atomics) | 直接共享内存访问 (需要同步) |
同步 | 通过事件循环隐式,SharedArrayBuffer 显式 | 显式 (互斥锁、条件变量、原子操作) |
复杂性 | 对于基本异步任务更简单 | 由于显式的线程管理和同步而更复杂 |
用例 | UI 响应性、I/O 密集型任务 | CPU 密集型任务、高性能计算、实时系统 |
C++ 提供了对线程和内存进行精细控制的工具,实现真正的并行和计算密集型任务的最大性能。然而,这种能力伴随着管理同步和避免常见并发陷阱的责任。
练习题:
- 解释并发和并行之间的区别。C++ 如何实现并行,JavaScript 如何实现并发?
- 什么是竞争条件,互斥锁如何帮助在 C++ 中防止它?提供一个简单的 C++ 代码示例,演示
std::mutex
的使用。 - 描述 C++ 中
std::async
和std::future
的用途。它们如何促进异步编程,这与 JavaScript 的async/await
如何比较?
项目构想:
- 在 C++ 中实现一个简单的多线程素数计算器。将要检查的数字范围分配给多个线程。使用
std::thread
创建线程,并使用std::mutex
或std::atomic
安全地收集每个线程找到的素数。将其执行时间与单线程版本进行比较。