一、切片append方法不是原子操作
多个协程操作同一个切片,使用append方法添加元素时,存在并发安全问题,需要对append方法加锁
func example() {
// 添加互斥锁
var lock sync.Mutex
eg := errgroup.Group{}
res := make([]int, 0)
for i := 0; i < 100; i++ {
eg.Go(func() error {
lock.Lock() // 加锁
res = append(res, i)
lock.Unlock() // 解锁
return nil
})
}
// 等待并发请求全部完成
_ = eg.Wait()
fmt.Println(res)
}
二、所有for循环里的协程对于for变量是可见的
如下所示例子,obj变量对于所有协程都是可见的,如果不先用临时变量进行赋值(oneObj := obj) 那么res切片中存储的都是同一个元素
type Obj struct {
Name string
Age int
}
func example2() {
eg := errgroup.Group{}
in := make([]Obj, 10)
for i := 0; i < 10; i++ {
in = append(in, Obj{
Number: i,
})
}
for _, obj := range in {
// 需要先进行赋值
oneObj := obj
eg.Go(func() error {
fmt.Println(oneObj)
return nil
})
}
// 等待并发请求全部完成
_ = eg.Wait()
}
三、errgroup的使用
推荐使用Kratos 改进的 errgroup,Kratos对errgroup做了一些扩展,加入了 recover和并行数的errgroup,err 中包含详细堆栈信息(coredump 发生时),使得 errgroup 机制更加健壮。
四、内存可见性,happens-before规则
1、import package时
如果package p下引入了package q,那么package q 的 init 函数执行先于 package p 后面执行任何其他代码
2、创建协程时
创建函数本身先于 goroutine 内的第一行代码执行。
3、销毁协程时
goroutine销毁事件并不保证先于所有的程序内的其他事件,销毁事件是不可见的
4、Channel通信
- channel 的 closing 操作先于 channel receive操作返回0值。因为返回0值是channel关闭导致的
- 对于初始化有缓存的channel(var c = make(chan int, 10)),send 操作先于 receive 操作
- 对于初始化无缓存的 channel(var c = make(chan int)), receive 操作先于 send 操作
- 对于初始化有缓存的channel(var c = make(chan int, C)),如果缓存的长度是C,那么第 K 个元素的 receive 操作先于 第 K+C 个元素 的 send 操作
5、锁
- 对任意 sync.Mutex 和 sync.RWMutex 的锁变量 L,且n < m,那么第 n 次调用 L.Unlock() 逻辑先于(结果可见于)第 m 次调用 L.Lock() 操作
- 对 sync.RWMutex 类型的锁变量 L,L.Unlock( ) 可见于 L.Rlock( ) ,第 n 次的 L.Runlock( ) 先于 第 n+1 次的 L.Lock()