2020 Go并发库回顾

距离我上一次在Gopher China 2019分享《Go并发编程实践》已经一年多了,后来也给北京的朋友准备了一个8小时的深入Go并发编程研讨课,如今一年过去了,go 的并发库又有哪些有趣的变化呢?本文带你回顾一下Go标准库中的并发类型的变动。

本文按照时间序的方式回顾Go标准同步库的变化,主要关注正式代码,对于单元测试,忽略对单元测试库的补充修改,只关注测试代码修改的bug。

Once.Do注解

深入Go并发编程研讨课讲解标准库中的同步原语的时候,我就举过这个例子,为什么下面的Once.Do的实现有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package wrong
import "sync/atomic"
type Once struct {
done uint32
}
func (o *Once) Do(f func()) {
if !atomic.CompareAndSwapUint32(&o.done, 0, 1) {
return
}
f()
}

不能使用atomic.CompareAndSwap实现Once.Do。使用atomic.CompareAndSwap设置标志是可以的,但是如果使用它来实现Once.Do,很可能会导致并发的goroutine会获取到还未初始化完全的资源。

比如下面的例子,如果你运行它的化,会报panic: assignment to entry in nil map错误。

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
package main
import (
"sync/atomic"
"time"
)
type Once struct {
done uint32
}
var v map[int]bool
func (o *Once) Do(f func()) {
if !atomic.CompareAndSwapUint32(&o.done, 0, 1) {
return
}
f()
}
func myinit() {
time.Sleep(time.Minute)
v = make(map[int]bool)
}
func main() {
var once Once
for i := 0; i < 10; i++ {
i := i
go func() {
once.Do(myinit)
v[i] = true
}()
}
time.Sleep(time.Minute)
}

所以Russ Cox专门在在代码中加上了一段注释,介绍不能使用atomic.CompareAndSwap原因。

那么如何实现一个安全的Once呢?你可以考虑考虑。

sync.Mutex的优化

issue#33747

sync.Map增加了一个新方法

修复单元测试中对channel的错误使用

引入staic lock ranking探测死锁的方法

修复net/textproto的并发错误

semaphore特定场景下unblock waiters

https://github.com/golang/sync/commit/43a5402ce75a95522677f77c619865d66b8c57ab