【C/C++】多线程读写普通 int 变量的一些问题

多线程读写普通int变量问题

场景:线程A写普通int,线程B读同一个普通int,无锁、无volatile

前提:int在当前平台是32位,对齐访问

1、会不会出现"字节撕裂(半写值)"?

  • x86/x64:对齐的32位int,读写是原子的CPU指令,写的时候不会读到一半字节,不会读到一个一半旧一半新的畸形数值。

⚠️ 注意:64位long long在32位系统就不是原子,会出现撕裂;但32位对齐int不会。

但!原子 ≠ 线程安全!这是最容易踩坑的地方。

虽然不会读到破碎值,但依然有两个严重问题:可见性问题指令重排序问题


2、可见性问题(CPU缓存)

每个CPU核心有私有L1/L2缓存。

  1. 线程A(CPU0)修改int,新值先写到CPU0缓存,不一定立刻刷回主内存
  2. 线程B(CPU1)读变量,读的是CPU1自己缓存里的旧副本。

现象:

A已经把x=100,B可能长期读到旧值x=0,永远看不到新修改。

这就是缓存可见性失效

3、编译器 / CPU指令重排序

编译器、CPU会为性能打乱指令执行顺序,只要单线程逻辑不变。

示例伪代码:

c 复制代码
int flag = 0;
int data = 0;

//线程A
data = 123;
flag = 1;  //通知线程B数据准备好了

//线程B
if(flag == 1){
    print(data);
}

重排序后,线程A实际执行顺序可能变成:

c 复制代码
flag = 1;
data = 123;

线程B看到flag=1,去读data,读到还没更新的旧值。

普通int没有任何屏障,编译器和CPU都可以重排读写。

4、总结现象(普通int,无volatile无锁)

  1. ✅ 对齐32‑bit int:不会发生值撕裂,不会读到半个更新的乱码数字
  2. 读线程可能一直读到旧值(缓存可见性)
  3. 指令重排序,读写顺序被打乱,业务逻辑错乱
  4. ❌ 不能保证读到最新写入的值

很多人误以为:int读写原子就不用锁,这是错误。原子只保证不会读到破碎字节,不保证可见性、不保证顺序


5、怎么解决

C/C++

  1. volatile int x
    • 阻止编译器优化,但是不能阻止CPU硬件重排序、缓存同步,现代多核下不完全可靠,不推荐用于线程同步。
  2. std::atomic<int> x ✅推荐
    • 硬件原子读写,自带内存屏障,解决可见性+重排序。读x.load(),写x.store()
  3. 互斥锁mutex保护读写。

Java

  • volatile int x:禁止重排序、保证可见性;32位int读写原子。

补充边界案例

  1. 如果int没有内存对齐 (刻意偏移),哪怕32位int,读写不再是CPU原子操作,就会出现值撕裂,读到一半旧一半新的数值。
  2. 64位long long,32位操作系统,普通读写不是原子,一定会撕裂。

最简记忆

对齐32位int:读写指令原子,但不代表线程安全。原子=不会碎;线程安全=可见+有序。两者不是一回事。

如果你需要,我可以写一小段可复现的C++示例代码,演示"写了但是读不到新值"这个现象。

相关推荐
卓怡学长17 分钟前
w175基于springboot校园二手物品交易平台
java·spring boot·spring·maven·intellij-idea
chen<>19 分钟前
malloc 和 free 背后发生了什么?
java·linux·服务器·操作系统
sunzy_泽阳21 分钟前
Java + Spring 实现 Hermes Agent:从源码看多模型接入、子代理、人审与沙箱
java
2601_9611332826 分钟前
同花顺期货通指标公式通达信指标
java
Freak嵌入式32 分钟前
树莓派 Pico GPIO 深度解析:从 MCU 架构到寄存器控制,底层原理与实践指南
java·开发语言·科技·单片机·嵌入式硬件·架构
互联网中的一颗神经元34 分钟前
09. mheap:全局堆管理器
java·spring·dubbo
小二李35 分钟前
第12章 nestjs服务端开发:RBAC权限系统设计
java·linux·前端
蒸蒸yyyyzwd36 分钟前
cpp web server 面试可能问题总结
c++·笔记·八股
啊啊啊啊啊!!!!39 分钟前
【c++】map和set的使用
开发语言·c++