当前位置: 首页 > news >正文

什么是原子变量

一、什么是原子变量(Atomic Variable)?

在多线程程序中,多个线程可能会同时访问和修改同一个变量。例如,两个线程都想给一个计数器加1,如果没有妥善处理,就可能出现“丢失更新”的情况:两个线程都读取到相同的数值,然后都+1,最后就只有一个加了1,实际上应该加了2。

原子变量就像“神奇的变量”,保证在多个线程同时操作时,每次操作都完整、不可被打断,就像是一只看得见的透明“锁”,不让其他操作干涉。

总结:

原子变量确保在多线程环境下,每次读/写操作都是完整不可中断的,避免竞态条件。


二、C++11中的原子变量是什么?怎么用?

在C++11标准中,核心工具是 <atomic> 头文件。它定义了模板类 std::atomic<T>,用于创建原子变量。

基本使用方式:

#include <atomic>
#include <thread>
#include <iostream>int main() {std::atomic<int> counter(0); // 初始化原子变量,初始值为0auto increment = [&]() {for(int i=0; i<10000; ++i) {counter++; // 原子自加}};std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Counter: " << counter << std::endl; // 输出应为20000
}

在这个例子中,两个线程同时对 counter 增加10000次,没有丢失数据,最后输出就是20000。


三、原子变量的关键操作和概念

1. 初始化

可以在声明时初始化,也可以之后赋值。

std::atomic<int> a(10);       // 直接初始化值10
std::atomic<int> b;           // 默认初始化,值未定义(但一般会初始化为0)
b.store(5);                   // 可以用store()设置值

2. 读写

  • 读取值:a.load() 或 使用隐式转换(如 int x = a;
  • 赋值:a.store(val) 或 a = val;

3. 自增、自减、加法等

  • 自增:a++ 或 ++a(两者都原子化)
  • 减:a-- 或 --a
  • 其他操作:fetch_addfetch_sub

例如:

复制代码

a.fetch_add(1);  // 原子加1

4. 内存序(Memory Order)

  • 默认std::memory_order_seq_cst(顺序一致性)
  • 其他memory_order_relaxedmemory_order_acquirememory_order_release 等,用于优化性能或特殊同步需求。

对于大部分普通应用,使用默认的顺序就可以。


四、原子操作的优势和局限

优势

  • 保证操作的原子性:多个线程同时操作时,不会出现“半途而废”的情况。
  • 避免锁的使用:写代码简单,性能较好(适合大量简单操作)。

局限

  • 原子操作只保证一行操作的原子性,复杂的多步操作还需要额外同步(比如使用std::mutex)。
  • 不能解决所有同步问题:比如两个变量的同步,需要用锁或更复杂的同步机制。

五、实用技巧和注意事项

1. 不要手动操作原子变量的内部状态

使用operator++operator--等,确保代码简洁。

2. 有序访问的理解

如果你在多线程中使用原子变量,通常只关心“什么时候读到的值”和“什么时候写入的值”。记住:

  • load() 和 store():读写操作
  • fetch_add() 和 fetch_sub():原子加减
  • 复合操作(比如a = b + c:可能需要锁或原子操作组合实现。

3. 原子类型的选择

对于普通整型,直接用std::atomic<int>等。

如果你处理的是复杂类型(如struct),需要确保它满足<atomic>的要求,或者用std::atomic<std::shared_ptr<T>>


六、总结:通俗版名词总结

内容说明
原子变量(Atomic)不让“别人”打断的变量,保证“读到”和“写入”都是完整的。
操作方式(a++/fetch_add对变量进行自增、加减、赋值等,都是“原子”的(不会被其他线程插嘴打断)。
作用场景多线程环境下,统计计数、标志状态等。坎儿少,效率高。
需要注意的点复杂操作仍可能需要锁,原子操作只能保证单个操作的原子性。

一、多线程竞争与同步的基础

假设你和朋友(两个线程)在做一个简单的任务,比如一起写数字到屏幕上或者统计某个内容的出现次数。

这时候,共享的“工具”就是一个变量,比如一个整数。

问题来了:

如果两个线程同时想“加1”,你会担心他们会干扰彼此——比如说:

  • 线程A读到变量值是5,准备加1变成6
  • 线程B也读到变量值还是5,准备加1变成6
  • 两个线程都写回6,但是实际上总共加了两次,应该是加了2,变成7

这就叫“竞态条件”或“数据竞争”。如果没有同步措施,结果就可能扭曲。


二、互斥锁(Mutex)——解决同步的“工具”

1. 什么是互斥锁?

用个比喻:

想象你有一把锁,只允许一个人使用。

当你要访问某个“重要的”变量时,先得“锁住”它(加锁),用完后再“解锁”。

这样,就保证在你用它的这段时间,其他人不能同时改。

2. 举个例子

#include <iostream>
#include <thread>
#include <mutex> // 互斥锁相关函数int count = 0;
std::mutex mtx; // 定义一个互斥锁void increment() {for (int i = 0; i < 10000; ++i) {mtx.lock();   // 上锁:保证这段代码是专属的++count;      // 保护共享变量mtx.unlock(); // 解锁,其他线程可以继续}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Final count: " << count << std::endl;
}

效果:

  • 只有一个线程在“锁”之后工作,直到解锁
  • 不会出现两个线程同时进入临界区(应该被保护的代码区)
  • 最终count的值一定是20000(因为每个线程都正确加了10,000)

三、互斥锁的弊端

虽然简单,但有一些缺点:

  • 性能开销:锁的获得和释放会带来一定的性能浪费,特别是频繁操作时。
  • 潜在死锁:如果程序设计不当,可能“死锁”——两个线程都在等对方先释放锁,永远都不放。

四、引入“原子”——更轻量级的同步机制

1. 为什么不用锁?

刚刚说过,锁虽然简单,但可能影响性能,特别是锁争用严重时。

2. 原子变量的基本思想

  • 原子变量相当于“特殊的变量”——它可以保证“自己内部”的操作是完整的,不会被其他线程打断。
  • 这样不用加锁,也不会出现“两个线程同时操作”的竞争问题。

五、从“锁”到“原子”——示意图

传统方法(用锁)原子方法
先获取锁,再操作变量,然后释放锁直接调用原子操作,内部已经保证安全

比喻:

用锁就像你用一把锁保护财宝(变量),别人得等你放开锁才能访问。

用原子变量就像“自己带着一个保护罩”,无论谁同时“触摸”它,都保证完整(原子)操作。


六、用C++11的原子变量(std::atomic)实现

你可以用std::atomic<int>来定义变量:

#include <atomic>
#include <thread>
#include <iostream>std::atomic<int> count(0); //定义一个原子变量void increment() {for (int i=0; i<10000; ++i) {count++;                // 这个自加就是原子操作// 或者写成:count.fetch_add(1);}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "最终值:" << count << std::endl; // 结果一定是20000
}

核心理解:

count++count.fetch_add(1)这些操作,内部都保证“自己全程不可被打断”,即每次都是完整的、原子的。


七、小结比较

方式是否保证原子性开销复杂度
互斥锁(mutex)Yes较大(锁开销)代码写法较复杂
原子变量(atomic)Yes小(低锁开销)代码简洁,操作直观

八、总结:为什么要用原子变量?

  • 简单高效:对简单的“加减”操作特别合适
  • 避免锁的开销和死锁风险
  • 用法直观:像普通变量一样操作,内部保证线程安全

 

相关文章:

  • 今日行情明日机会——20250506
  • The 2023 ICPC Asia Taoyuan Regional Programming Contest
  • C++自动重连机制设计与实现指南
  • 2025ACTF Web部分题解
  • Linux/AndroidOS中进程间的通信线程间的同步 - POSIX IPC
  • 【MongoDB篇】MongoDB的事务操作!
  • LeetCode 1128. 等价多米诺骨牌对的数量 题解
  • C++ STL 基础与多线程安全性说明文档
  • Visual Studio 快捷键更改和设置
  • 使用ip池后,爬虫还被封,是什么原因呢?
  • Zoho在2025:三个支点和一个新故事
  • 数据管理平台是什么?企业应如何做好数据化管理?
  • MySQL数据库中篇
  • 【STM32项目实战】一文了解单片机的SPI驱动外设功能
  • Vue3 中用 canvas 封装抽奖转盘组件:设定中奖概率及奖项图标和名称
  • LeetCode:二叉树的中序遍历
  • 【SpringBoot】SpringBoot中使用AOP实现日志记录功能
  • [HOT 100] 2646. 最小化旅行的价格总和
  • 前景理论——AI与思维模型【95】
  • 量子跃迁:破解未来计算的“时空密码”​
  • 纪念苏联伟大卫国战争胜利80周年阅兵彩排,解放军仪仗队亮相
  • 中国难以承受高关税压力?外交部:任何外部冲击都改变不了中国经济基本面
  • 央行:今日起下调再贷款利率0.25个百分点
  • 中方对原产印度进口氯氰菊酯实施反倾销措施,商务部回应
  • 中国公民免签赴马来西亚的停留天数如何计算?使馆明确
  • 《黎明的一切》:与正常世界脱轨后,我选择不再回去