# C++并发编程:线程与同步的艺术
从C++11标准正式引入线程库开始,并发编程就不再需要依赖操作系统特定的API。这门语言为开发者提供了一套完整的工具集,用于表达线程间的协作与约束。理解这些工具的适用场景,是编写可靠多线程程序的基础。
## 基础同步原语的选择与应用
互斥锁(`std::mutex`)是保护共享数据的基本手段。然而,直接调用`lock()`和`unlock()`容易在异常抛出时导致死锁。RAII风格的封装是更稳健的做法:`std::lock_guard`在构造时加锁,析构时自动解锁,将资源生命周期与作用域绑定。
```cpp
#include
#include
std::mutex mtx;
std::vector
<"efc.j9k5.org.cn"><"wew.j9k5.org.cn"><"edc.j9k5.org.cn">
void add_data(int value) {
std::lock_guard
shared_data.push_back(value);
}
```
当同步逻辑涉及条件判断时,仅靠互斥锁是不够的。如果线程需要等待某个条件成立,轮询检查会浪费CPU资源。条件变量(`std::condition_variable`)允许线程在条件不满足时进入等待状态,并在条件改变时被唤醒,这是一种更高效的协作方式。
```cpp
#include
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker_thread() {
std::unique_lock
cv.wait(lock, []{ return ready; }); // 自动判断条件
// 执行后续操作
}
```
对于简单的计数器或标志位,原子类型(`std::atomic`)提供了无锁的线程安全操作,其开销低于互斥锁。`fetch_add`、`compare_exchange_weak`等方法是实现无锁数据结构的基础。
## 并行算法的演进与适用边界
C++17将并发支持提升到了算法层面。通过执行策略,开发者可以要求标准库算法并行执行。`std::execution::par`允许算法在多个线程上执行,而`std::execution::par_unseq`则额外允许向量化等指令级并行。
```cpp
#include
#include
#include
std::vector
// 并行处理每个元素
std::for_each(std::execution::par, data.begin(), data.end(),
[](double& x) { x = complex_computation(x); });
```
并行算法并非适用于所有场景。当数据规模较小时,线程创建和同步的开销可能超过并行带来的收益。此外,若操作中使用了需要互斥锁的共享资源,`par_unseq`策略可能导致死锁——因为同一线程交错执行多次获取锁的操作是阻塞的。在实际应用中,需要根据数据量和计算密度权衡是否启用并行。
## 面向未来的并发模型
C++20引入了协程,为异步编程提供了新的语法支持。协程允许函数挂起执行并在后续恢复,而不会阻塞线程。这种机制特别适合I/O密集型任务,可以在等待操作完成时让出线程资源。
即将到来的C++26将带来sender/receiver模型,这是一种更灵活的异步编排方式。它通过组合操作来描述计算图,调度器决定任务在哪个线程上执行。例如,使用`bulk`算法可以轻松将循环转换为并行执行:
```cpp
auto snd = stdexec::schedule(sched)
| stdexec::bulk(max_y, [=](int y) {
for (int x = 0; x < max_x; ++x) {
compute_pixel(x, y);
<"eav.j9k5.org.cn"><"efg.j9k5.org.cn"><"wwf.j9k5.org.cn">
}
});
```
这种声明式风格将任务的描述与执行解耦,开发者可以专注于数据流依赖,而将线程管理交给运行时。
从基础的互斥锁到高阶的并行算法,C++并发编程的演进始终在平衡控制力与抽象层级。理解每种工具的设计意图和适用边界,才能写出既正确又高效的并发程序。