在C++多线程编程中,当多个线程同时访问并修改同一个共享变量,且访问顺序会影响程序执行结果时,就会产生竞态条件。这种问题会导致程序出现不可预期的行为,比如计算结果错误、数据丢失等,是多线程开发中最需要重点解决的问题之一。

竞态条件的产生原因
竞态条件的本质是线程对共享资源的操作不是原子性的。比如一个线程执行i = i + 1的操作,实际会拆分为读取i的值、计算i+1、将结果写回i三个步骤。如果两个线程同时执行这个操作,就可能出现两个线程都读取到相同的旧值,最终写回的结果只增加了一次,而不是两次。
下面是一段会产生竞态条件的示例代码:
#include <iostream>
#include <thread>
#include <vector>
int shared_counter = 0;
// 线程执行的函数,对共享变量做累加
void increment() {
for (int i = 0; i < 10000; i++) {
shared_counter++; // 非原子操作,会产生竞态条件
}
}
int main() {
std::vector<std::thread> threads;
// 创建5个线程同时执行累加操作
for (int i = 0; i < 5; i++) {
threads.emplace_back(increment);
}
// 等待所有线程执行完成
for (auto& t : threads) {
t.join();
}
// 预期结果是50000,实际运行结果往往小于这个值
std::cout << "最终计数器值: " << shared_counter << std::endl;
return 0;
}
原子变量的核心特性
C++11标准引入了std::atomic模板类,用于定义原子变量。原子变量的操作在多线程环境下是不可分割的,要么完全执行,要么完全不执行,不会出现执行到一半被其他线程打断的情况,因此可以从根本上避免竞态条件。
原子变量支持多种操作,包括读取、写入、交换、算术运算等,这些操作都是原子性的。同时原子变量还支持内存顺序的设置,可以根据场景选择不同的内存顺序来平衡性能和正确性,默认的内存顺序已经能满足大部分场景的需求。
用原子变量解决竞态条件的方案
要解决上面示例中的竞态条件,只需要把共享变量替换为std::atomic类型的原子变量即可,具体修改如下:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic> // 引入原子变量头文件
// 定义原子类型的计数器,初始化为0
std::atomic<int> shared_counter(0);
void increment() {
for (int i = 0; i < 10000; i++) {
// 原子变量的自增操作是原子性的,不会产生竞态条件
shared_counter++;
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 5; i++) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
// 此时输出结果一定是50000
std::cout << "最终计数器值: " << shared_counter << std::endl;
return 0;
}
原子变量的其他常见应用场景
作为线程间的标志位
可以用原子布尔变量作为线程退出的标志,避免多个线程同时修改标志位导致的状态不一致问题:
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
std::atomic<bool> is_running(true); // 原子标志位,初始为运行态
void worker_thread() {
while (is_running.load()) { // 原子读取标志位
// 执行线程任务
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "工作线程退出" << std::endl;
}
int main() {
std::thread t(worker_thread);
// 主线程运行1秒后设置标志位为false
std::this_thread::sleep_for(std::chrono::seconds(1));
is_running.store(false); // 原子写入标志位
t.join();
std::cout << "主线程退出" << std::endl;
return 0;
}
实现简单的无锁计数器
原子变量可以实现无锁的计数器,不需要使用互斥锁就能保证线程安全,性能比互斥锁更高:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
std::atomic<int> request_count(0);
// 模拟处理请求的函数
void handle_request() {
// 处理请求逻辑
// 原子增加请求计数
request_count.fetch_add(1, std::memory_order_relaxed);
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; i++) {
threads.emplace_back([i]() {
for (int j = 0; j < 1000; j++) {
handle_request();
}
});
}
for (auto& t : threads) {
t.join();
}
std::cout << "总处理请求数: " << request_count << std::endl;
return 0;
}
原子变量使用的注意事项
- 原子变量只适合操作单个变量,如果需要同时操作多个共享变量,原子变量无法保证整体操作的原子性,此时需要使用互斥锁等同步机制。
- 不要对原子变量使用普通的赋值、运算操作之外的非原子操作,比如对原子变量取地址后进行指针操作,会破坏原子性。
- 默认的内存顺序已经能满足大部分场景,除非对性能有极高要求,否则不需要手动调整内存顺序,避免引入难以排查的问题。
- 原子变量的性能虽然比互斥锁高,但如果操作非常频繁,还是需要结合实际场景测试,选择最合适的同步方案。
C++多线程竞态条件原子变量std_atomic线程同步修改时间:2026-07-24 07:12:26