前言
一、std::atomic
作用
- 提供对基本数据类型(如整型、指针、布尔值等)的原子操作。
- 原子操作意味着对变量的读写不会被线程切换打断,保证操作的完整性和一致性。
特点
- 无锁(lock-free)操作,效率高,适合简单的状态标志或计数器。
- 只能用于单个变量的操作,例如
atomic<int>
,不能直接用来等待或通知线程。 - 不会阻塞线程,访问原子变量时线程不会被挂起。
典型用途
- 实现简单的线程间标志(如“是否准备好”)。
- 计数器、状态标志等轻量级同步。
二、std::condition_variable
作用
- 用于线程间的等待和通知机制。
- 一个线程可以等待某个条件成立(阻塞等待),另一个线程在条件满足时通知它。
特点
- 需要和互斥锁(
std::mutex
)配合使用,保证条件的检查和等待过程的原子性。 - 线程等待时会挂起(阻塞),不会占用 CPU,直到被通知唤醒。
- 支持复杂的线程同步逻辑,比如等待某个条件满足后再继续执行。
典型用途
- 多线程协作时,实现“等待某件事完成再继续”的场景。
- 任务队列中,生产者-消费者模型中的通知机制。
三、简单对比表格
特性/方面 | std::atomic | std::condition_variable |
---|---|---|
功能 | 原子读写、简单同步 | 线程等待和通知机制 |
线程阻塞 | 不阻塞(非阻塞操作) | 阻塞(线程挂起等待通知) |
适用场景 | 简单状态标志、计数器 | 等待复杂条件,线程间通知 |
是否需要互斥锁 | 不需要 | 需要配合 std::mutex 使用 |
CPU 占用 | 不阻塞,不会挂起 | 等待时挂起,不占用 CPU |
实现复杂度 | 简单,直接操作变量 | 需要配合锁和条件判断,稍复杂 |
四、总结
std::atomic
适合用来快速、简单地同步单个变量的状态,不会阻塞线程。std::condition_variable
适合用来等待复杂条件、实现线程间的通知,线程等待时会挂起,不浪费 CPU。- std::atomic 是一个多线程原子数据,提供了一种线程安全的方式来访问和修改共享数据,而无需使用显式的互斥锁,如果是简单数据,使用这个是非常方便的。但是它不提供阻塞等待机制,也就是说,如果主线程只是循环检查
ready
,会一直占用 CPU(忙等待,busy-wait)。
在实际开发中,往往会把两者结合起来使用:用 std::atomic
维护状态,用 std::condition_variable
实现等待通知机制。
以下是一个案例:
测试程序
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <chrono> // 用于模拟工作耗时
// 全局同步变量
std::atomic<bool> ready(false); // 标记工作线程是否准备好
std::mutex mtx; // 互斥锁,配合条件变量使用
std::condition_variable cv; // 条件变量,用于线程等待和通知
void preparatory_worker_thread() {
std::cout << "准备工作线程开始准备工作:\n";
for(int i=0;i<=3;i++){
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时工作
std::cout << "正在准备工作...\n";
}
// 准备工作完成,设置 ready 标志
ready = true;
// 通知等待的线程(主线程)
cv.notify_one();
std::cout << "准备工作完成,通知主线程。\n";
}
int main() {
std::thread worker(preparatory_worker_thread);
std::cout << "主线程等待准备工作线程完毕...\n";
// 等待工作线程准备完成
std::unique_lock<std::mutex> lock(mtx);
// lambda 里面检查 ready 是否为 true,如果不是就阻塞等待
cv.wait(lock, [] { return ready.load(); });
std::cout << "主线程检测到准备工作线程准备完成,继续执行。\n";
worker.join();
std::cout << "主线程工作...\n";
return 0;
}