共计 3037 个字符,预计需要花费 8 分钟才能阅读完成。
前言
之前文章中介绍的互斥锁尽管可能保障同串行化,然而却保障不了执行过程中的中断。
要么胜利、要么失败,没有中断的状况,咱们叫它叫原子性,这种由硬件 CPU 提供反对的个性,是十分牢靠的。
百度百科上对于原子操作的介绍。
原子操作
由 sync/atomic 包提供操作反对。
加法(add)
实现累加
func TestDemo1(t *testing.T) {
var counter int64 = 0
for i := 0; i < 100; i++ {go func() {atomic.AddInt64(&counter, 1)
}()}
time.Sleep(2 * time.Second)
log.Println("counter:", atomic.LoadInt64(&counter))
}
后果
=== RUN TestDemo1
2020/10/11 00:24:56 counter: 100
--- PASS: TestDemo1 (2.00s)
PASS
减法(add)
对于做减法,是没有间接提供的办法的,而 Add(-1)这种是不能对 uint 类型应用的,能够通过补码的形式实现
func TestDemo2(t *testing.T) {
var counter uint64 = 100
for i := 0; i < 100; i++ {go func() {atomic.AddUint64(&counter, ^uint64(-(-1)-1))
}()}
time.Sleep(2 * time.Second)
log.Println("counter:", atomic.LoadUint64(&counter))
}
后果
=== RUN TestDemo2
2020/10/11 00:32:05 counter: 0
--- PASS: TestDemo2 (2.00s)
PASS
比拟并替换(compare and swap,简称 CAS)
并发编程中,在没有应用互斥锁的前提下,对共享数据先取出做判断,再依据判断的后果做后续操作,必然是会出问题的,应用 CAS 能够防止这种问题。
func TestDemo3(t *testing.T) {
var first int64 = 0
for i := 1; i <= 10000; i++ {go func(i int) {if atomic.CompareAndSwapInt64(&first, 0, int64(i)) {log.Println("领先运行的是 goroutine", i)
}
}(i)
}
time.Sleep(2 * time.Second)
log.Println("num:", atomic.LoadInt64(&first))
}
后果
=== RUN TestDemo3
2020/10/11 00:42:10 领先运行的是 goroutine 3
2020/10/11 00:42:12 num: 3
--- PASS: TestDemo3 (2.01s)
PASS
加载(load)
加载操作在进行时只会有一个,不会有其它的读写操作同时进行。
func TestDemo4(t *testing.T) {
var counter int64 = 0
for i := 0; i < 100; i++ {go func() {atomic.AddInt64(&counter, 1)
log.Println("counter:", atomic.LoadInt64(&counter))
}()}
time.Sleep(2 * time.Second)
}
存储(store)
存储操作在进行时只会有一个,不会有其它的读写操作同时进行。
func TestDemo5(t *testing.T) {
var counter int64 = 0
for i := 0; i < 10; i++ {go func(i int) {atomic.StoreInt64(&counter, int64(i))
log.Println("counter:", atomic.LoadInt64(&counter))
}(i)
}
time.Sleep(2 * time.Second)
}
替换(swap)
swap 办法返回被替换之前的旧值。
func TestDemo6(t *testing.T) {
var counter int64 = 0
for i := 0; i < 10; i++ {go func(i int) {log.Println("counter old:", atomic.SwapInt64(&counter, int64(i)))
}(i)
}
time.Sleep(2 * time.Second)
}
后果
=== RUN TestDemo6
2020/10/11 00:43:36 counter old: 0
2020/10/11 00:43:36 counter old: 9
2020/10/11 00:43:36 counter old: 5
2020/10/11 00:43:36 counter old: 1
2020/10/11 00:43:36 counter old: 2
2020/10/11 00:43:36 counter old: 3
2020/10/11 00:43:36 counter old: 6
2020/10/11 00:43:36 counter old: 4
2020/10/11 00:43:36 counter old: 7
2020/10/11 00:43:36 counter old: 0
--- PASS: TestDemo6 (2.00s)
PASS
原子值(value)
value 是一个构造体,外部值定义为 interface{},所以它是能够承受任何类型的值。
第一次赋值的时候,原子值的类型就确认了,前面不能赋值其它类型的值。
func TestDemo7(t *testing.T) {
var value atomic.Value
var counter uint64 = 1
value.Store(counter)
log.Println("counter:", value.Load())
value.Store(uint64(10))
log.Println("counter:", value.Load())
value.Store(100) // 引发 panic
log.Println("counter:", value.Load())
time.Sleep(2 * time.Second)
}
后果
=== RUN TestDemo7
2020/10/11 10:14:58 counter: 0
2020/10/11 10:14:58 counter: 10
--- FAIL: TestDemo7 (0.00s)
panic: sync/atomic: store of inconsistently typed value into Value [recovered]
panic: sync/atomic: store of inconsistently typed value into Value
...
Process finished with exit code 1
扩大
无锁编程
此处临时先介绍一下,前面有机会出文章再一起学习提高。
放弃互斥锁,采纳原子操作,常见办法有以下几种:
针对计数器
能够应用例如下面介绍的 Add 办法。
单生产、消费者
单生产者、单消费者能够做到免锁拜访环形缓冲区(Ring Buffer)。\
比方,Linux kernel 中的 kfifo 的实现。
RCU(Read Copy Update)
新旧正本切换机制,对于旧正本能够采纳提早开释的做法。
CAS(Compare And Swap)
如无锁栈,无锁队列期待
总结
- 原子操作性能是高于互斥锁的,但带来的复杂性也会进步,真正用好并不容易。
- 互斥锁、条件变量,办法外部的实现也都用到了原子操作,特地是 CAS。
文章示例代码
正文完