并发保护

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()
}

2.【必须】禁止并发写map

  • 并发写map容易造成程序崩溃并异常退出,建议加锁保护
    // bad
    func main() {
      m := make(map[int]int)
      //并发读写
      go func() {
          for {
              _ = m[1] 
          }
      }()
      go func() {
          for {
              m[2] = 1
          }
      }()
      select {}
    }

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
}
最后编辑: kuteng  文档更新时间: 2021-06-04 17:24   作者:kuteng