原子操作(atomic operation)是一种计算机操作,它在执行过程中是不可分割的,要么完全成功,要么完全失败。它具有以下几个关键特性:
-
不可分割性:
- 在操作完成之前,其他操作不能插入或干扰。换句话说,原子操作的执行是“原子”的,类似于原子不可分割一样,它要么全部完成,要么完全不执行。
-
不可见性:
- 对于其他操作来说,原子操作的中间状态是不可见的。这意味着在原子操作完成之前,其他线程或进程不能观察到其执行过程中的任何中间状态。
-
并发安全性:
- 原子操作在多线程或多处理器系统中确保了对共享数据的安全访问,避免了竞争条件(race conditions)和数据不一致性。
原子操作的实现
原子操作通常由硬件提供支持,现代处理器通常提供对原子操作的原生支持,如原子加法、原子交换等。这些操作通常通过特定的指令实现,例如:
-
Compare-And-Swap (CAS):比较内存中的值与指定的值,如果它们相等,则将内存中的值更新为新的值。CAS 是实现许多锁和同步机制的基础。
-
Test-And-Set:测试一个值并将其设置为新值,通常用于实现互斥锁(mutexes)。
-
Load-Exclusive 和 Store-Exclusive:如 ARM 架构中的
LDREX
和STREX
指令,它们用于实现原子更新操作。
举例说明:
atomic_inc:
ldrex r0, [r1]
add r0, r0, #1
strex r2, r0, r1
cmp r2, #0
bne atomic_inc
这段汇编代码实现了一个原子自增操作,通常用于多线程或多处理器系统中,以确保对共享数据的安全访问。它使用了ARM架构中的LDREX
和STREX
指令来确保操作的原子性。以下是对这段代码的详细解析:
代码解析
-
ldrex r0, [r1]
:- 从内存地址
r1
读取一个值,并将其加载到寄存器r0
。 LDREX
(Load-Exclusive)指令用于从指定地址加载一个值到寄存器r0
,同时在内部设置一个独占标志(exclusive flag),表示对该地址的独占访问。这个标志用于后续的STREX
指令来验证操作的原子性。
- 从内存地址
-
add r0, r0, #1
:- 将寄存器
r0
中的值增加1。 - 这是进行自增操作的步骤,将
r0
中的值加上立即数#1
,并将结果存回r0
。
- 将寄存器
-
strex r2, r0, r1
:- 尝试将寄存器
r0
中的新值存储到内存地址r1
,并将结果状态存储在寄存器r2
中。 STREX
(Store-Exclusive)指令用于将寄存器r0
中的值存储到内存地址r1
,同时检查独占标志。如果存储成功,r2
的值会被设置为0
;如果存储失败(由于其他处理器或线程对该地址进行了修改),r2
的值会被设置为非零。
- 尝试将寄存器
-
cmp r2, #0
:- 比较寄存器
r2
的值与0
。 CMP
(Compare)指令用于将r2
的值与0
进行比较,以检查STREX
指令是否成功。
- 比较寄存器
-
bne atomic_inc
:- 如果比较结果不等于
0
(即r2
的值不为0
),则跳转到atomic_inc
标签处重新尝试。 BNE
(Branch if Not Equal)指令用于在r2
的值不为0
时跳转到atomic_inc
标签。这样,如果STREX
指令由于独占标志无效而失败,就会重新尝试操作,直到成功为止。
- 如果比较结果不等于
这段代码实现了一个原子自增操作,步骤如下:
- 读取内存地址
r1
中的值。 - 对值进行自增。
- 尝试将自增后的值写回内存地址
r1
。 - 如果由于其他线程或处理器的干预导致写回失败,则重试,直到成功为止。
这种原子操作在多线程或多处理器环境中非常重要,能够确保对共享数据的正确更新,而不会被并发的操作干扰。