go sync atomic
提示
本文代码基于 go1.17.13,src/sync/atomic/目录下的 value.go、doc.go、asm.s
# 一、简述
- atomic 包提供了原子操作,用于在多个 goroutine 之间进行安全的并发访问和修改共享变量,原子操作是一种不可分割的操作,可以保证在并发环境下的数据安全性;
- atomic 包中提供了一系列的函数,用于对基本数据类型(如整数、指针)进行原子操作,包括读取、存储、加法、减法、比较等;
- 在 doc.go 文件中定义了一些列原子操作函数的原型,这些操作函数的间接实现在 asm.s 文件中,最终实现是在
src/runtime/internal/atomic
目录下的文件中,不同的平台和版本对应着不同的汇编文件; - value.go 文件中定义了一个原子数据类型
Value
,并提供了原子操作函数,对于非基本数据类型的原子操作可以通过复用该类型完成,本文主要对该类型进行简单介绍;
# 二、基本原理
- atomic 包的原子操作实际上是通过底层硬件指令或操作系统提供的原子操作来实现的;
- 这些原子操作能够保证在并发环境下执行时是不可中断的,不会被其他线程或 goroutine 打断;
- 具体原理取决于底层硬件和操作系统的支持,通常涉及到对内存的原子读写操作,以及对特定寄存器或内存位置的锁定和解锁;
# 三、基本用法
点击查看
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println("Counter:", atomic.LoadInt64(&counter))
}
// output: Counter: 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 使用
atomic.AddInt64
函数对 counter 变量进行原子加法操作,并使用atomic.LoadInt64
函数原子地读取 counter 的值; - 通过使用原子操作,确保了并发访问 counter 变量时的数据一致性和安全性;
# 四、源码解读
# 1, Value
// 。Value 的零值从 Load 返回 nil。
// 调用 Store 后,Value 不得被复制。首次使用后 Value 不得被复制。
type Value struct {
v interface{}
}
// 接口的内部表达式
type ifaceWords struct {
typ unsafe.Pointer // 存储值的类型
data unsafe.Pointer // 存储具体的值
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Value
提供一致类型化值的原子加载和存储;- 调用
Load
后,返回的零值为nil
; - 调用
Store
后,Value
不得被复制; - 首次使用后
Value
不得被复制;
# 2, Load()
点击查看
func (v *Value) Load() (val interface{}) {
vp := (*ifaceWords)(unsafe.Pointer(v))
typ := LoadPointer(&vp.typ)
if typ == nil || uintptr(typ) == ^uintptr(0) {
// 首次存储还没有完成
return nil
}
data := LoadPointer(&vp.data)
vlp := (*ifaceWords)(unsafe.Pointer(&val))
vlp.typ = typ
vlp.data = data
return
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Load
返回由Store
最新设置的值,如果没有为此值调用Store
,则返回nil
;
# 3, Store()
点击查看
func (v *Value) Store(val interface{}) {
if val == nil {
// 存储的值为 nil 直接p anic
panic("sync/atomic: store of nil value into Value")
}
// 通过类型转换将 v 和 val 转换为指向 ifaceWords 结构体的指针,以便能够直接访问 v 和 val 的底层数据
vp := (*ifaceWords)(unsafe.Pointer(v))
vlp := (*ifaceWords)(unsafe.Pointer(&val))
for {
// 循环的目的是确保原子操作成功,避免竞态条件
typ := LoadPointer(&vp.typ)
if typ == nil {
// 尝试启动第一次存储
// 禁止抢占以便其他 goroutine 能够使用主动自旋等待来等待完成,这样 GC 就不会意外地看到假类型。
runtime_procPin()
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
// 如果 vp 被其他 goroutine 初始化了,则继续 continue 等待
runtime_procUnpin()
continue
}
// 第一次存储已经完成,使用原子比较和交换(CompareAndSwapPointer)将 vp.typ 设置为 ^uintptr(0),表示 vp 已经被初始化
StorePointer(&vp.data, vlp.data)
StorePointer(&vp.typ, vlp.typ)
runtime_procUnpin()
return
}
if uintptr(typ) == ^uintptr(0) {
// 说明 vp 正在被其他 goroutine 初始化,此时暂时无法执行存储操作,需要继续循环等待
continue
}
// 对于非首次存储,校验类型并重写数据
if typ != vlp.typ {
// 类型不一致,直接 panic
panic("sync/atomic: store of inconsistently typed value into Value")
}
StorePointer(&vp.data, vlp.data)
return
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Store
将Value
的值设置为 x,对给定Value
的Store
的所有调用都必须使用相同的具体类型的值;Store
不一致的类型会panic
,Store(nil)
也是如此;- 先判断旧值的类型,旧值类型为 nil 时,说明当前是第一次存储,通过禁止抢占的以让当前 goroutine 能够通过 CAS 的方式存储值,存储成功的话则直接返回;存储失败的话则说明可能被其他 goroutine 已经存储了,需要循环等待;
- 再次获取旧值的类型并判断,如果类型为中间值,则说明正在被其他 goroutine 存储,继续循环等待;
- 如果旧值的类型不为空且不为中间值,则需要校验新存入值的类型,并重写数据;
# 4, Swap()
点击查看
func (v *Value) Swap(new interface{}) (old interface{}) {
if new == nil {
panic("sync/atomic: swap of nil value into Value")
}
// 基本逻辑,同 Store
vp := (*ifaceWords)(unsafe.Pointer(v))
np := (*ifaceWords)(unsafe.Pointer(&new))
for {
typ := LoadPointer(&vp.typ)
if typ == nil {
runtime_procPin()
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin()
continue
}
StorePointer(&vp.data, np.data)
StorePointer(&vp.typ, np.typ)
runtime_procUnpin()
return nil // 首次存储,说明当前 Value 为空,直接返回 nil
}
if uintptr(typ) == ^uintptr(0) {
// 说明 vp 正在被其他 goroutine 初始化,此时暂时无法执行存储操作,需要继续循环等待
continue
}
// 对于非首次存储,校验类型并重写数据
if typ != np.typ {
panic("sync/atomic: swap of inconsistently typed value into Value")
}
op := (*ifaceWords)(unsafe.Pointer(&old))
// 用新值指针替换旧值指针,并返回旧值指针
op.typ, op.data = np.typ, SwapPointer(&vp.data, np.data)
return old
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Swap
将 new 存储到Value
中,并返回之前存储的值,如果Valu
e 为空,则返回 nil;Swap
的所有调用都必须使用相同具体类型的值,不一致类型的Swap
会panic
,Swap(nil)
也会panic;Swap
是将Store
和Load
操作的结合体,基本原理和Store
一致;
# 5, CompareAndSwap()
点击查看
func (v *Value) CompareAndSwap(old, new interface{}) (swapped bool) {
if new == nil {
panic("sync/atomic: compare and swap of nil value into Value")
}
vp := (*ifaceWords)(unsafe.Pointer(v))
np := (*ifaceWords)(unsafe.Pointer(&new))
op := (*ifaceWords)(unsafe.Pointer(&old))
if op.typ != nil && np.typ != op.typ {
// 类型为空或不一致,panic
panic("sync/atomic: compare and swap of inconsistently typed values")
}
for {
// 原子加载当前值的类型
typ := LoadPointer(&vp.typ)
if typ == nil {
if old != nil {
// 值不相等,返回 false
return false
}
// 值相等,且为空,则赋新值
runtime_procPin()
if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
runtime_procUnpin()
continue
}
// 旧值相等,且新值赋值成功,返回 true
StorePointer(&vp.data, np.data)
StorePointer(&vp.typ, np.typ)
runtime_procUnpin()
return true
}
if uintptr(typ) == ^uintptr(0) {
// 说明 vp 正在被其他 goroutine 初始化,此时暂时无法执行存储操作,需要继续循环等待
continue
}
// 当前值类型与新值类型不一致,panic
if typ != np.typ {
panic("sync/atomic: compare and swap of inconsistently typed value into Value")
}
// 原子加载获取当前值
data := LoadPointer(&vp.data)
var i interface{}
(*ifaceWords)(unsafe.Pointer(&i)).typ = typ
(*ifaceWords)(unsafe.Pointer(&i)).data = data
if i != old {
// 当前值不相等,直接返回 false
return false
}
// 原子对比并替换值指针
return CompareAndSwapPointer(&vp.data, data, np.data)
}
}
func runtime_procPin()
func runtime_procUnpin()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
CompareAndSwap
对Value
执行比较和交换操作;CompareAndSwap
的所有调用都必须使用相同具体类型的值,不一致类型的CompareAndSwap
和CompareAndSwap(old, nil)
一样会 panic;CompareAndSwap
基本原理和Swap
一致,只是多了一个值比较的操作;runtime_procPin
函数用于将当前的 goroutine 与所在的操作系统线程进行绑定,这意味着 goroutine 将会始终在该操作系统线程上执行,这种绑定关系在某些需要固定线程执行环境的场景中非常有用,比如需要与特定的操作系统资源绑定、避免线程切换开销等;runtime_procUnpin
函数用于解除当前 goroutine 与操作系统线程的绑定,这样 goroutine 就可以自由地在不同的操作系统线程上执行,这通常用于需要动态调度 goroutine 到不同线程上执行的场景,以充分利用多核处理器和实现更好的并发性能;
编辑 (opens new window)
上次更新: 2023/12/10, 22:13:21