找回密码
 立即注册
首页 业界区 业界 Golang基础笔记十五之sync

Golang基础笔记十五之sync

昝沛珊 昨天 20:33
本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十五之sync
这一篇笔记介绍 Golang 中的 sync 模块。
sync 包主要提供了基础的同步原语,比如互斥锁,读写锁,等待组等,用于解决并发编程中的线程安全问题,以下是本篇笔记目录:

  • WaitGroup-等待组
  • sync.Mutex-互斥锁
  • sync.RWMutex-读写锁
  • sync.Once-一次性执行
  • sync.Pool-对象池
  • sync.Cond-条件变量
  • sync.Map
1、WaitGroup-等待组

前面在第十篇我们介绍 goroutine 和 channel 的时候,在使用 goroutine 的时候介绍有一段代码如下:
  1. package main
  2. import (
  3.     "fmt"
  4.     "time"
  5. )
  6. func PrintGoroutineInfo() {
  7.     fmt.Println("msg from goroutine")
  8. }
  9. func main() {
  10.     go PrintGoroutineInfo()
  11.     time.Sleep(1 * time.Millisecond)
  12.     fmt.Println("msg from main")
  13. }
复制代码
在这里,我们开启了一个协程调用 PrintGoroutineInfo() 函数,然后使用 time.Sleep() 来等待它调用结束。
然而在开发中,我们不能确定这个函数多久才能调用完毕,也无法使用准确的 sleep 时间来等待,那么这里就可以使用到 sync 模块的 WaitGroup 函数来等待一个或多个 goroutine 执行完毕。
下面是使用示例:
  1. package main
  2. import (
  3.     "fmt"
  4.     "math/rand"
  5.     "sync"
  6.     "time"
  7. )
  8. func SleepRandSeconds(wg *sync.WaitGroup) {
  9.     defer wg.Done()
  10.     sleepSeconds := rand.Intn(3)
  11.     fmt.Printf("sleep %d seconds\n", sleepSeconds)
  12.     time.Sleep(time.Duration(sleepSeconds) * time.Second)
  13. }
  14. func main() {
  15.     var wg sync.WaitGroup
  16.     wg.Add(2)
  17.     go SleepRandSeconds(&wg)
  18.     go SleepRandSeconds(&wg)
  19.     wg.Wait()
  20.     fmt.Println("函数执行完毕")
  21. }
复制代码
在这里,我们通过 var wg sync.WaitGroup 定义了一个等待组,并通过 wg.Add(2) 表示添加了需要等待的并发数,在并发中我们将 &wg 传入并通过 wg.Done() 减少需要等待的并发数。
在 wg.Done() 函数内部,使用 wg.Add(-1) 减少需要等待的并发数,在 main 函数中,使用 wg.Wait() 进入阻塞状态,当等待的并发都完成后,此函数就会返回,完成等待并接着往后执行。
2、sync.Mutex-互斥锁

1. 数据竞态与互斥锁

当多个 goroutine 并发访问同一个共享资源,且至少有一个访问是写操作时,就会发生数据竞态,造成的结果就是程序每次运行的结果表现可能会不一致。
比如下面的示例:
  1. var balance int
  2. func AddFunc() {
  3.     balance += 1
  4. }
  5. func main() {
  6.     for range 100 {
  7.         go AddFunc()
  8.     }
  9.     time.Sleep(5 * time.Second)
  10.     fmt.Println("balance is: ", balance)
  11. }
复制代码
多次执行上面的代码,最终输出的 balance 的值可能都不一致。
如果一个变量在多个 goroutine 同时访问时,不会出现比如数据不一致或程序崩溃的情况,那么我们就称其是并发安全的。
我们可以使用 go run -race main.go 的方式来检测数据竞态,执行检测后,会输出数据竞态的一些信息,比如发生在代码的多少行,一共发生了多少次数据竞态:
  1. ==================
  2. WARNING: DATA RACE
  3. Read at 0x000003910df8 by goroutine 7:
  4.   main.AddFunc()
  5.       /../main.go:13 +0x24
  6. Previous write at 0x000003910df8 by goroutine 6:
  7.   main.AddFunc()
  8.       /../main.go:13 +0x3c
  9. Goroutine 7 (running) created at:
  10.   main.main()
  11.       /../main.go:18 +0x32
  12. Goroutine 6 (finished) created at:
  13.   main.main()
  14.    /../main.go:18 +0x32
  15.    
  16. ==================
  17. balance is:  98
  18. Found 3 data race(s)
  19. exit status 66
复制代码
而要避免这种数据竞态的发生,我们可以限制在同一时间只能有一个 goroutine 访问同一个变量,这种方法称为互斥机制。
我们可以通过缓冲通道和 sync.Mutex 来实现这种互斥锁的操作。
2. 缓冲通道实现互斥锁

我们可以通过容量为 1 的缓冲通道来实现互斥锁的操作,保证同一时间只有一个 goroutine 访问同一个变量,下面是修改后的代码:
[code]var sema = make(chan struct{}, 1)var balance intfunc AddFunc() {    sema
您需要登录后才可以回帖 登录 | 立即注册