1.9 并发保护
1.9 并发保护
1.9.1【必须】禁止在闭包中直接调用循环变量
- 在循环中启动协程,当协程中使用到了循环的索引值,由于多个协程同时使用同一个变量会产生数据竞争,造成执行结果异常。
// bad
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
var group sync.WaitGroup
for i := 0; i < 5; i++ {
group.Add(1)
go func() {
defer group.Done()
fmt.Printf("%-2d", i) //这里打印的i不是所期望的
}()
}
group.Wait()
}
// good
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
var group sync.WaitGroup
for i := 0; i < 5; i++ {
group.Add(1)
go func(j int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in start()")
}
group.Done()
}()
fmt.Printf("%-2d", j) // 闭包内部使用局部变量
}(i) // 把循环变量显式地传给协程
}
group.Wait()
}
1.9.2【必须】禁止并发写map
- 并发写map容易造成程序崩溃并异常退出,建议加锁保护 ``` // bad func main() { m := make(map[int]int) //并发读写 go func() { for { _ = m[1] } }() go func() { for { m[2] = 1 } }() select {} }
#### 1.9.3【必须】确保并发安全
敏感操作如果未作并发安全限制,可导致数据读写异常,造成业务逻辑限制被绕过。可通过同步锁或者原子操作进行防护。
通过同步锁共享内存
// good var count int func Count(lock *sync.Mutex) { lock.Lock()// 加写锁 count++ fmt.Println(count) lock.Unlock()// 解写锁,任何一个Lock()或RLock()均需要保证对应有Unlock()或RUnlock() }
func main() { lock := &sync.Mutex{} for i := 0; i < 10; i++ { go Count(lock) //传递指针是为了防止函数内的锁和调用锁不一致 } for { lock.Lock() c := count lock.Unlock() runtime.Gosched()//交出时间片给协程 if c > 10 { break } } }
* 使用`sync/atomic`执行原子操作
// good import ( "sync" "sync/atomic" )
func main() { type Map map[string]string var m atomic.Value m.Store(make(Map)) var mu sync.Mutex // used only by writers read := func(key string) (val string) { m1 := m.Load().(Map) return m1[key] } insert := func(key, val string) { mu.Lock() // 与潜在写入同步 defer mu.Unlock() m1 := m.Load().(Map) // 导入struct当前数据 m2 := make(Map) // 创建新值 for k, v := range m1 { m2[k] = v } m2[key] = val m.Store(m2) // 用新的替代当前对象 } _, _ = read, insert } ```
