【JUC并发】cmpxchg和lock指令

一、背景介绍

在多线程或多处理器系统中,多个处理器可能同时访问和修改同一片内存数据。为了确保数据一致性和操作的原子性,处理器需要提供机制来同步对共享内存的访问。CMPXCHG 和 LOCK 指令就是用于实现这些同步机制的关键。

二、CMPXCHG 指令详解

1. 指令概述

全称:Compare and Exchange(比较并交换)。

功能 :CMPXCHG 指令用于 原子性地 比较寄存器和内存(或寄存器)中的值,如果相等,则将新的值写入目的操作数;如果不相等,则更新寄存器中的值。

2. 指令格式

通用格式:CMPXCHG 目的操作数, 源操作数

操作数

目的操作数(dest):可以是内存地址或寄存器。

源操作数(src):必须是寄存器。

3. 工作原理

步骤

  1. 比较 :将 EAX 寄存器的值与 目的操作数 的值进行比较。

  2. 相等时 :如果 EAX == dest,则将 源操作数 的值写入 目的操作数(dest = src)。

  3. 不相等时 :如果 EAX != dest,则将 目的操作数 的值加载到 EAX 寄存器中(EAX = dest)。

标志位更新ZF(Zero Flag):如果比较相等,ZF = 1;否则,ZF = 0。

复制代码
初始化

mov EAX, 5         ; 将 EAX 设为期望的旧值 5

mov EBX, 10        ; 新值为 10

; 尝试将内存地址 [myVar] 的值从 5 改为 10

CMPXCHG [myVar], EBX

; 检查 ZF 标志位

je success         ; 如果 ZF = 1,跳转到成功处理

jne failure        ; 如果 ZF = 0,跳转到失败处理

success:

; 操作成功,继续执行

failure:

; 操作失败,EAX 已更新为 [myVar] 的当前值,可选择重试

4. 应用场景

实现原子性操作 :CMPXCHG 可用于实现 无锁算法 中的原子性比较和交换操作。

常用于 :实现 自旋锁无锁队列原子变量 等并发数据结构。

三、LOCK 前缀详解

1. 指令概述

功能 :LOCK 前缀用于 锁定总线,使随后的指令在多处理器环境下以原子方式执行,防止其他处理器同时访问被锁定的内存区域。

2. 使用方式

格式:LOCK 指令

支持的指令 :ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR 等 修改内存操作数 的指令。

3. 工作原理

总线锁定

• 在多处理器系统中,LOCK 前缀会在指令执行期间锁定处理器与内存之间的总线或缓存一致性机制,确保该指令对内存的读-改-写操作是原子的。

缓存锁定

• 现代处理器通常使用缓存一致性协议(如 MESI)进行缓存锁定,而不是锁定总线,以提高性能。

复制代码
; 原子性地将内存地址 [myVar] 的值加 1

LOCK INC [myVar]

解释:LOCK 前缀确保 INC [myVar] 在多处理器环境下以原子方式执行,其他处理器无法在此过程中访问 myVar。

4. 注意事项

性能影响: 由于 LOCK 指令会导致总线或缓存的锁定,可能带来性能开销,应谨慎使用。

对齐要求 : 被操作的内存地址应当是 自然对齐 的,否则可能导致锁无法正确生效。

四、CMPXCHG 与 LOCK 的结合

1. 原子性的保证

自动加锁 :当 CMPXCHG 指令的 目的操作数内存地址 时,处理器会自动对指令进行加锁,无需显式添加 LOCK 前缀。

复制代码
CMPXCHG [myVar], EBX ; 当 [myVar] 是内存地址时,自动具有原子性​​​​​​​

2. 显式使用 LOCK 前缀

必要性:对于某些指令,如果需要在多处理器环境下保证原子性,需要显式添加 LOCK 前缀。

复制代码
LOCK XADD [myVar], EBX ; 原子性地交换并加​​​​​​​

3. 实现原子操作

原子加法:LOCK ADD [myVar], EAX

原子交换:LOCK XCHG [myVar], EAX

原子比较并交换 :CMPXCHG [myVar], EAX ; 自动具有原子性

五、CMPXCHG 和 LOCK 在多线程编程中的应用

1. 实现自旋锁

自旋锁概念:自旋锁是一种轻量级的锁机制,线程在等待锁的过程中不会被阻塞,而是不断轮询锁的状态。

实现示例

复制代码
; 尝试获取锁

spin_lock:

    MOV EAX, 1

    XCHG EAX, [lockVar]

    CMP EAX, 0

    JNZ spin_lock ; 如果锁已被占用,继续自旋



; 临界区代码

    ; ...



; 释放锁

    MOV [lockVar], 0

解释:XCHG 指令在对内存操作数进行操作时,自动具有锁定性质,确保原子性。

2. 实现无锁数据结构

无锁编程:利用 CMPXCHG 等原子指令,实现不需要锁的并发数据结构,如无锁队列、无锁栈。

示例:无锁栈的入栈操作

cpp 复制代码
void push(node_t* new_node) {

    node_t* old_head;

    do {

        old_head = head;

        new_node->next = old_head;

    } while (!cmpxchg(&head, old_head, new_node));

}

解释:cmpxchg 函数使用 CMPXCHG 指令,尝试将 head 从 old_head 更新为 new_node。如果失败,说明 head 已被其他线程修改,需要重试。

六、缓存一致性协议与 LOCK 指令

1. 缓存一致性协议

MESI 协议:现代处理器使用 MESI 等缓存一致性协议,确保缓存中的数据在多处理器环境下保持一致。

2. LOCK 指令的实现

缓存锁定

• 当对 缓存线 进行操作时,处理器会通过缓存一致性协议锁定缓存线,而不是锁定总线,提高效率。

非对齐访问的限制:如果操作的内存地址跨越多个缓存线,处理器无法通过缓存锁定实现原子性,此时可能需要锁定总线,性能下降。

七、总结

1. CMPXCHG 指令

用途:用于原子性地比较并交换数据。

特点:当目的操作数为内存地址时,自动具有原子性,无需显式 LOCK。

2. LOCK 前缀

用途:用于确保后续指令在多处理器环境下的原子性操作。

影响:可能导致总线或缓存锁定,带来性能开销。

3. 应用场景

多线程同步:实现原子变量、自旋锁、无锁数据结构等。

并发控制:确保对共享内存的访问在多处理器环境下的正确性。

4. 注意事项​​​​​​​

性能考虑:应尽量减少 LOCK 指令的使用范围,避免长时间锁定总线或缓存。

内存对齐:确保被操作的内存地址是自然对齐的,避免跨缓存线操作。

相关推荐
jieyucx1 小时前
Go 语言核心关键字:defer 深度解析与实战避坑
开发语言·后端·golang·defer
南囝coding2 小时前
Anthropic 内部数百个 Claude Code Skills,他们总结的这套方法值得看
前端·后端
Rust研习社3 小时前
Ubuntu 全面拥抱 Rust 后,我意识到 Rust 社区要变了
linux·服务器·开发语言·后端·ubuntu·rust
ComputerInBook3 小时前
X64 汇编 MOVSD 的两种用法
汇编·汇编指令·movsd
小江的记录本3 小时前
【AI大模型选型指南】《2026年5月(最新版)国内外主流AI大模型选型指南》(个人版)
前端·人工智能·后端·ai·aigc·ai编程·ai写作
我叫黑大帅3 小时前
基于 Docker + Watchtower 自动化部署后端服务
后端·docker·面试
fox_lht4 小时前
12.3.使用生命周期使引用一直有用
开发语言·后端·rust
萧曵 丶4 小时前
JUC 实际业务高频面试题浅谈
java·juc·aqs·lock
fengxin_rou4 小时前
用户模块架构实战:DTO 与 Domain 分层、Optional 空值处理、事务只读优化详解
java·后端·架构·用户实战
程序员cxuan5 小时前
看了一下姚顺宇的访谈,确实太顶了。
人工智能·后端·程序员