原子操作的定义

原子(atom)本意是“不能被进一步分割的最小粒子”,而所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。


硬件支持

处理器通过缓存加锁或总线加锁方式来实现。


原子操作的函数

GCC内部提供的原子操作接口,主要针对Intel架构处理器,其它处理不保证可用。

函数 说明
type __sync_fetch_and_add (type *ptr, type value, …) 将value加到ptr上,结果更新到ptr,并返回操作之前*ptr的值
type __sync_fetch_and_sub (type *ptr, type value, …) ptr减去value,结果更新到ptr,并返回操作之前*ptr的值
type __sync_fetch_and_or (type *ptr, type value, …) ptr与value相或,结果更新到ptr, 并返回操作之前*ptr的值
type __sync_fetch_and_and (type *ptr, type value, …) ptr与value相与,结果更新到ptr,并返回操作之前*ptr的值
type __sync_fetch_and_xor (type *ptr, type value, …) ptr与value异或,结果更新到ptr,并返回操作之前*ptr的值
type __sync_fetch_and_nand (type *ptr, type value, …) ptr取反后,与value相与,结果更新到ptr,并返回操作之前*ptr的值
type __sync_add_and_fetch (type *ptr, type value, …) 将value加到ptr上,结果更新到ptr,并返回操作之后新*ptr的值
type __sync_sub_and_fetch (type *ptr, type value, …) ptr减去value,结果更新到ptr,并返回操作之后新*ptr的值
type __sync_or_and_fetch (type *ptr, type value, …) ptr与value相或, 结果更新到ptr,并返回操作之后新*ptr的值
type __sync_and_and_fetch (type *ptr, type value, …) ptr与value相与,结果更新到ptr,并返回操作之后新*ptr的值
type __sync_xor_and_fetch (type *ptr, type value, …) ptr与value异或,结果更新到ptr,并返回操作之后新*ptr的值
type __sync_nand_and_fetch (type *ptr, type value, …) ptr取反后,与value相与,结果更新到ptr,并返回操作之后新*ptr的值
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, …) 比较ptr与oldval的值,如果两者相等,则将newval更新到ptr并返回true
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, …) 比较ptr与oldval的值,如果两者相等,则将newval更新到ptr并返回操作之前*ptr的值
__sync_synchronize (…) 发出完整内存栅栏
type __sync_lock_test_and_set (type *ptr, type value, …) 将value写入ptr,对ptr加锁,并返回操作之前*ptr的值。即,try spinlock语义
void __sync_lock_release (type *ptr, …) 将0写入到ptr,并对ptr解锁。即,unlock spinlock语义


例子

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
  long value = 2;
  printf("value=%ld\n", __sync_add_and_fetch(&value, 3));

  return 0;
}

我分别用gcc,mingw32和android clang编译器,基于x86_64和armv7a处理器架构进行编译生成汇编代码。

$ gcc -S atom.c -o atom.s
$ x86_64-w64-mingw32-gcc -S atom.c -o atom_mingw32.s
$ x86_64-linux-android26-clang -S atom.c -o atom_clang_x86_64.s
$ armv7a-linux-androideabi26-clang -S atom.c -o atom_clang_armv7a.s
	.file	"atom.c"
	.text
	.section	.rodata
.LC0:
	.string	"value=%ld\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB6:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movl	%edi, -20(%rbp)
	movq	%rsi, -32(%rbp)
	movq	$2, -8(%rbp)
	movl	$3, %eax
	lock xaddq	%rax, -8(%rbp)
	addq	$3, %rax
	movq	%rax, %rsi
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE6:
	.size	main, .-main
	.ident	"GCC: (Debian 8.3.0-6) 8.3.0"
	.section	.note.GNU-stack,"",@progbits
	.file	"atom.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
.LC0:
	.ascii "value=%ld\12\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	movl	%ecx, 16(%rbp)
	movq	%rdx, 24(%rbp)
	call	__main
	movl	$2, -4(%rbp)
	movl	$3, %eax
	lock xaddl	%eax, -4(%rbp)
	addl	$3, %eax
	movl	%eax, %edx
	leaq	.LC0(%rip), %rcx
	call	printf
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (GNU) 8.3-posix 20190406"
	.def	printf;	.scl	2;	.type	32;	.endef
	.text
	.file	"atom.c"
	.globl	main                    # -- Begin function main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
	.cfi_startproc
# %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$32, %rsp
	movl	$0, -4(%rbp)
	movl	%edi, -8(%rbp)
	movq	%rsi, -16(%rbp)
	movq	$2, -24(%rbp)
	movl	$3, %esi
	lock		xaddq	%rsi, -24(%rbp)
	addq	$3, %rsi
	leaq	.L.str(%rip), %rdi
	movb	$0, %al
	callq	printf@PLT
	xorl	%ecx, %ecx
	movl	%eax, -28(%rbp)         # 4-byte Spill
	movl	%ecx, %eax
	addq	$32, %rsp
	popq	%rbp
	.cfi_def_cfa %rsp, 8
	retq
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
	.cfi_endproc
                                        # -- End function
	.type	.L.str,@object          # @.str
	.section	.rodata.str1.1,"aMS",@progbits,1
.L.str:
	.asciz	"value=%ld\n"
	.size	.L.str, 11


	.ident	"Android (5220042 based on r346389c) clang version 8.0.7 (https://android.googlesource.com/toolchain/clang b55f2d4ebfd35bf643d27dbca1bb228957008617) (https://android.googlesource.com/toolchain/llvm 3c393fe7a7e13b0fba4ac75a01aa683d7a5b11cd) (based on LLVM 8.0.7svn)"
	.section	".note.GNU-stack","",@progbits

可以看到x86_64架构下生成的汇编代码基本一至,其中lock xaddq就是原子操作。除了lock前缀,所以x打头的指令也代表原子操作。


text
	.syntax unified
	.eabi_attribute	67, "2.09"	@ Tag_conformance
	.eabi_attribute	6, 10	@ Tag_CPU_arch
	.eabi_attribute	7, 65	@ Tag_CPU_arch_profile
	.eabi_attribute	8, 1	@ Tag_ARM_ISA_use
	.eabi_attribute	9, 2	@ Tag_THUMB_ISA_use
	.fpu	neon
	.eabi_attribute	34, 1	@ Tag_CPU_unaligned_access
	.eabi_attribute	15, 1	@ Tag_ABI_PCS_RW_data
	.eabi_attribute	16, 1	@ Tag_ABI_PCS_RO_data
	.eabi_attribute	17, 2	@ Tag_ABI_PCS_GOT_use
	.eabi_attribute	20, 1	@ Tag_ABI_FP_denormal
	.eabi_attribute	21, 1	@ Tag_ABI_FP_exceptions
	.eabi_attribute	23, 3	@ Tag_ABI_FP_number_model
	.eabi_attribute	24, 1	@ Tag_ABI_align_needed
	.eabi_attribute	25, 1	@ Tag_ABI_align_preserved
	.eabi_attribute	38, 1	@ Tag_ABI_FP_16bit_format
	.eabi_attribute	18, 4	@ Tag_ABI_PCS_wchar_t
	.eabi_attribute	26, 2	@ Tag_ABI_enum_size
	.eabi_attribute	14, 0	@ Tag_ABI_PCS_R9_use
	.file	"atom.c"
	.globl	main                    @ -- Begin function main
	.p2align	2
	.type	main,%function
	.code	32                      @ @main
main:
	.fnstart
@ %bb.0:
	.save	{r11, lr}
	push	{r11, lr}
	.setfp	r11, sp
	mov	r11, sp
	.pad	#24
	sub	sp, sp, #24
	movw	r2, #0
	str	r2, [r11, #-4]
	str	r0, [r11, #-8]
	str	r1, [sp, #12]
	movw	r0, #2
	str	r0, [sp, #8]
	dmb	ish
.LBB0_1:                                @ =>This Inner Loop Header: Depth=1
	add	r0, sp, #8
	ldrex	r0, [r0]
	add	r1, r0, #3
	add	r2, sp, #8
	strex	r3, r1, [r2]
	cmp	r3, #0
	str	r0, [sp, #4]            @ 4-byte Spill
	bne	.LBB0_1
@ %bb.2:
	ldr	r0, .LCPI0_0
.LPC0_0:
	add	r0, pc, r0
	dmb	ish
	ldr	r1, [sp, #4]            @ 4-byte Reload
	add	r1, r1, #3
	bl	printf
	movw	r1, #0
	str	r0, [sp]                @ 4-byte Spill
	mov	r0, r1
	mov	sp, r11
	pop	{r11, pc}
	.p2align	2
@ %bb.3:
.LCPI0_0:
	.long	.L.str-(.LPC0_0+8)
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
	.cantunwind
	.fnend
                                        @ -- End function
	.type	.L.str,%object          @ @.str
	.section	.rodata.str1.1,"aMS",%progbits,1
.L.str:
	.asciz	"value=%ld\n"
	.size	.L.str, 11


	.ident	"Android (5220042 based on r346389c) clang version 8.0.7 (https://android.googlesource.com/toolchain/clang b55f2d4ebfd35bf643d27dbca1bb228957008617) (https://android.googlesource.com/toolchain/llvm 3c393fe7a7e13b0fba4ac75a01aa683d7a5b11cd) (based on LLVM 8.0.7svn)"
	.section	".note.GNU-stack","",%progbits

ARM架构下原子操作的的相关汇编指令是ldrex、strex。


原子操作的应用

全局自增序号应用

__sync_fetch_and_add = i++

__sync_add_and_fetch = ++i

互斥锁

互斥锁是一种数据结构,使你可以执行一系列互斥操作。而原子操作是互斥的单个操作,这意味着没有其他线程可以打断它。

所以说原子布尔类型只可以实现互斥锁的部分功能。

__sync_bool_compare_and_swap(&mutex, false, true) return true 加锁成功

__sync_bool_compare_and_swap(&mutex, true, false) return true 解锁成功

自旋锁

互斥锁会让资源申请者进入睡眠状态,而自旋锁不会引起调用者睡眠,会一直循环判断该锁是否成功获取。自旋锁是专为防止多处理器并发而引入的一种锁。

__sync_lock_test_and_set -> trylock

__sync_lock_release -> tryunlock