【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++示例代码,演示"写了但是读不到新值"这个现象。

相关推荐
西峰u3 小时前
Java单例模式|从基础到多线程安全
java·java单例模式
ClinicTech3 小时前
实验室洗瓶机的清洗系统架构与自动化控制解析
java·python
shada3 小时前
Boost.Process v2 在旧内核上报 `assign: Bad file descriptor`
c++
linmengmeng_13143 小时前
【总结】MyBatis-Plus批量插入与清表的正确姿势
java·spring boot·mysql·mybatis
木井巳3 小时前
【记忆化搜索】不同路径
java·算法·leetcode·深度优先·剪枝·推荐算法
Sam_Deep_Thinking3 小时前
new Thread()之后发生了什么?
java·后端·面试·程序员
泡海椒3 小时前
规则引擎对比:JQuick-Java vs Drools 轻量场景选型对比
java·开发语言
wno7043 小时前
Spring Security短信验证码登录
java·python·spring
别动我齐刘海3 小时前
ROS2 Jazzy + C++ 实战路线——基础学习2
c++·人工智能·vscode·python·学习·机器学习·机器人
步行cgn3 小时前
Spring util 命名空间详解
java·后端·spring