在C++多线程编程场景中,多个线程同时读写同一个共享变量时,很容易出现数据竞争问题,比如两个线程同时对一个计数器做自增操作,最终得到的结果可能小于预期值。std::atomic是C++11标准引入的原子类型模板,能够对指定类型的变量执行原子操作,从底层保证操作的不可分割性,从根源上避免数据竞争问题。
std::atomic的基本使用方式
std::atomic是一个模板类,支持大部分内置数据类型,比如int、long、bool、指针类型等,部分自定义类型如果满足特定条件也可以使用。使用std::atomic需要先包含<atomic>头文件,声明原子变量的方式和普通变量类似,只是类型需要包裹在std::atomic模板中。
下面是一个简单的计数器示例,展示普通变量和原子变量在多线程场景下的差异:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
// 普通计数器
int normal_counter = 0;
// 原子计数器
std::atomic<int> atomic_counter(0);
// 线程执行的函数,对计数器做自增操作
void increment(int times) {
for (int i = 0; i < times; ++i) {
// 普通变量自增,非原子操作
normal_counter++;
// 原子变量自增,原子操作
atomic_counter++;
}
}
int main() {
const int thread_num = 4;
const int increment_times = 10000;
std::vector<std::thread> threads;
// 创建4个线程同时执行自增操作
for (int i = 0; i < thread_num; ++i) {
threads.emplace_back(increment, increment_times);
}
// 等待所有线程执行完成
for (auto& t : threads) {
t.join();
}
std::cout << "普通计数器最终值: " << normal_counter << std::endl;
std::cout << "原子计数器最终值: " << atomic_counter.load() << std::endl;
return 0;
}
上述代码中,4个线程每个对计数器做10000次自增,理论上最终结果应该是40000。普通计数器因为自增操作不是原子的,最终结果通常会小于40000,而原子计数器的最终结果一定是40000,这就是std::atomic保证原子操作的效果。
std::atomic的常用操作
std::atomic提供了多种原子操作接口,常用的有以下几种:
- load():原子读取当前原子变量的值,默认使用内存顺序std::memory_order_seq_cst
- store(val):原子将值val写入原子变量
- fetch_add(val):原子自增,返回自增前的值
- fetch_sub(val):原子自减,返回自减前的值
- operator++/--:重载的自增自减运算符,执行原子自增自减操作
- compare_exchange_strong(expected, desired):比较交换操作,如果当前值等于expected,就原子替换为desired,返回是否替换成功
下面是一个使用compare_exchange_strong实现简单自旋锁的示例:
#include <atomic>
#include <thread>
#include <iostream>
// 自旋锁,0表示未锁定,1表示锁定
std::atomic<int> spin_lock(0);
// 加锁操作
void lock() {
int expected = 0;
// 循环尝试将锁从0改为1,直到成功
while (!spin_lock.compare_exchange_strong(expected, 1)) {
expected = 0; // 失败后重置expected,继续尝试
}
}
// 解锁操作
void unlock() {
spin_lock.store(0);
}
int shared_data = 0;
void thread_func(int id) {
lock();
// 临界区操作,修改共享数据
shared_data++;
std::cout << "线程" << id << "修改共享数据,当前值: " << shared_data << std::endl;
unlock();
}
int main() {
std::thread t1(thread_func, 1);
std::thread t2(thread_func, 2);
t1.join();
t2.join();
return 0;
}
原子操作的内存顺序
std::atomic的所有操作都支持指定内存顺序,内存顺序决定了原子操作之间的可见性和重排序规则,常用的内存顺序有以下几种:
| 内存顺序 | 说明 |
|---|---|
| std::memory_order_relaxed | 宽松内存顺序,只保证操作是原子的,不保证操作的顺序和可见性,性能最高 |
| std::memory_order_acquire | 获取操作,当前线程后续的读写操作不能被重排序到该操作之前,且能看到其他线程释放操作之前的所有写入 |
| std::memory_order_release | 释放操作,当前线程之前的读写操作不能被重排序到该操作之后,且之前的写入对其他线程的获取操作可见 |
| std::memory_order_seq_cst | 顺序一致内存顺序,默认使用的顺序,保证所有原子操作的全局顺序一致,性能相对较低但最安全 |
如果不需要特殊的内存顺序约束,直接使用默认的std::memory_order_seq_cst即可,大部分场景下都能满足需求。如果对性能要求极高,且明确知道内存顺序的约束,可以选择更宽松的内存顺序提升性能。
std::atomic实现多线程同步的常见方法
除了直接保证单个变量的原子操作,std::atomic还可以配合内存顺序实现多线程之间的同步,常见的场景有以下几种:
1. 标志位同步
使用一个原子布尔变量作为标志位,一个线程修改标志位,其他线程等待标志位变化,实现线程之间的通知。
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<bool> ready(false);
void worker_thread() {
// 等待主线程设置ready为true
while (!ready.load(std::memory_order_acquire)) {
// 空循环等待
}
std::cout << "工作线程开始执行任务" << std::endl;
}
int main() {
std::thread worker(worker_thread);
// 主线程做一些准备工作
std::cout << "主线程准备完成,通知工作线程" << std::endl;
ready.store(true, std::memory_order_release);
worker.join();
return 0;
}
2. 计数器同步
使用原子计数器统计完成的线程数量,当计数器达到指定值时,通知等待的线程继续执行,类似线程屏障的效果。
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
std::atomic<int> finished_count(0);
const int total_threads = 3;
void task_thread(int id) {
std::cout << "线程" << id << "执行任务完成" << std::endl;
finished_count.fetch_add(1, std::memory_order_acq_rel);
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < total_threads; ++i) {
threads.emplace_back(task_thread, i);
}
// 等待所有线程完成任务
while (finished_count.load(std::memory_order_acquire) < total_threads) {
// 空循环等待
}
std::cout << "所有线程任务完成,主线程继续执行" << std::endl;
for (auto& t : threads) {
t.join();
}
return 0;
}
使用std::atomic的注意事项
使用std::atomic时需要注意以下几点:
- 不是所有类型都可以作为std::atomic的模板参数,只有满足可平凡复制(TriviallyCopyable)的类型才能使用,否则编译会报错
- 原子操作的开销比普通操作要高,因为需要保证操作的原子性和内存顺序,不要对所有变量都使用std::atomic,只对多线程共享的变量使用
- std::atomic的默认构造函数不会初始化变量,需要显式初始化,避免未定义行为
- 对于需要同时保证多个变量原子性的场景,std::atomic无法单独满足,需要结合互斥锁等其他同步机制使用
总的来说,std::atomic是C++中实现轻量级原子操作和线程同步的重要工具,在只需要保证单个共享变量线程安全的场景下,比互斥锁的性能更好,是多线程开发中的常用组件。
std::atomicC++原子类型多线程同步原子操作线程安全修改时间:2026-06-28 00:03:45