go sync.RWMutex与sync.WaitGroup

102 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

问题

每次读写共享资源都要加锁的话,性能会十分低下。由于一般情况下写的时候不能同时读、读的时候不能同时写,但是读的时候可以同时读,所以使用读写锁可以显著提高性能。

读写锁

var (
   sum int
   mutex sync.RWMutex
)
func readSum() int {
   mutex.RLock()
   defer mutex.RUnlock()
   b := sum
   return b
}

通过只获取读锁,各个协程可以同时去读取数据,不需要等待一个协程读完再读,可以大大提高性能。

sync.WaitGroup

在一些代码中我们会有下面的代码:

time.Sleep(2 * time.Second)

这是让主函数等待两秒再返回,以免主函数返回退出后一些协程还没有执行完毕。但是有两个问题:

  1. 如果协程在两秒内执行完毕,主函数本该提前返回却等待两秒后再反回会造成性能问题
  2. 如果协程在两秒内仍未执行完毕,主函数返回退出,程序出现错误

Go语言为我们提供sync.WaitGroup,功能是一旦协程全部执行完毕,程序马上退出,这样可以解决上面的两个问题。

var (
   sum   int
   mutex sync.Mutex
   rwMutex sync.RWMutex
)
func main() {
   run()
}
func run() {
   var wg sync.WaitGroup
   wg.Add(110)
   for i := 0; i < 100; i++ {
      go func() {
         defer wg.Done()
         add(10)
      }()
   }
   for i := 0; i < 10; i++ {
      go func() {
         defer wg.Done()
         fmt.Println("sum:", readSum())
      }()
   }
   wg.Wait()
}

func add(i int) {
   sum += i
}
func readSum() int {
   rwMutex.RLock()
   defer rwMutex.RUnlock()
   b := sum
   return b
}

首先通过通过 wg.Add(110)将计数器置为协程的数量,执行完一个协程时通过defer wg.Done()将计数器值减一,通过wg.Wait()一直等待,知道计数器值为0,这时候所有的协程都执行完毕了。以上代码可以做到当110个协程执行完毕的时候程序退出。

sync.WaitGroup适合协调多个协程共同做同一个事情的场景。