本文首发于公众号:Hunter后端
原文链接:Golang基础笔记十五之sync
这一篇笔记介绍 Golang 中的 sync 模块。
sync 包主要提供了基础的同步原语,比如互斥锁,读写锁,等待组等,用于解决并发编程中的线程安全问题,以下是本篇笔记目录:
- WaitGroup-等待组
- sync.Mutex-互斥锁
- sync.RWMutex-读写锁
- sync.Once-一次性执行
- sync.Pool-对象池
- sync.Cond-条件变量
- sync.Map
1、WaitGroup-等待组
前面在第十篇我们介绍 goroutine 和 channel 的时候,在使用 goroutine 的时候介绍有一段代码如下:- package main
- import (
- "fmt"
- "time"
- )
- func PrintGoroutineInfo() {
- fmt.Println("msg from goroutine")
- }
- func main() {
- go PrintGoroutineInfo()
- time.Sleep(1 * time.Millisecond)
- fmt.Println("msg from main")
- }
复制代码 在这里,我们开启了一个协程调用 PrintGoroutineInfo() 函数,然后使用 time.Sleep() 来等待它调用结束。
然而在开发中,我们不能确定这个函数多久才能调用完毕,也无法使用准确的 sleep 时间来等待,那么这里就可以使用到 sync 模块的 WaitGroup 函数来等待一个或多个 goroutine 执行完毕。
下面是使用示例:- package main
- import (
- "fmt"
- "math/rand"
- "sync"
- "time"
- )
- func SleepRandSeconds(wg *sync.WaitGroup) {
- defer wg.Done()
- sleepSeconds := rand.Intn(3)
- fmt.Printf("sleep %d seconds\n", sleepSeconds)
- time.Sleep(time.Duration(sleepSeconds) * time.Second)
- }
- func main() {
- var wg sync.WaitGroup
- wg.Add(2)
- go SleepRandSeconds(&wg)
- go SleepRandSeconds(&wg)
- wg.Wait()
- fmt.Println("函数执行完毕")
- }
复制代码 在这里,我们通过 var wg sync.WaitGroup 定义了一个等待组,并通过 wg.Add(2) 表示添加了需要等待的并发数,在并发中我们将 &wg 传入并通过 wg.Done() 减少需要等待的并发数。
在 wg.Done() 函数内部,使用 wg.Add(-1) 减少需要等待的并发数,在 main 函数中,使用 wg.Wait() 进入阻塞状态,当等待的并发都完成后,此函数就会返回,完成等待并接着往后执行。
2、sync.Mutex-互斥锁
1. 数据竞态与互斥锁
当多个 goroutine 并发访问同一个共享资源,且至少有一个访问是写操作时,就会发生数据竞态,造成的结果就是程序每次运行的结果表现可能会不一致。
比如下面的示例:- var balance int
- func AddFunc() {
- balance += 1
- }
- func main() {
- for range 100 {
- go AddFunc()
- }
- time.Sleep(5 * time.Second)
- fmt.Println("balance is: ", balance)
- }
复制代码 多次执行上面的代码,最终输出的 balance 的值可能都不一致。
如果一个变量在多个 goroutine 同时访问时,不会出现比如数据不一致或程序崩溃的情况,那么我们就称其是并发安全的。
我们可以使用 go run -race main.go 的方式来检测数据竞态,执行检测后,会输出数据竞态的一些信息,比如发生在代码的多少行,一共发生了多少次数据竞态:- ==================
- WARNING: DATA RACE
- Read at 0x000003910df8 by goroutine 7:
- main.AddFunc()
- /../main.go:13 +0x24
- Previous write at 0x000003910df8 by goroutine 6:
- main.AddFunc()
- /../main.go:13 +0x3c
- Goroutine 7 (running) created at:
- main.main()
- /../main.go:18 +0x32
- Goroutine 6 (finished) created at:
- main.main()
- /../main.go:18 +0x32
-
- ==================
- balance is: 98
- Found 3 data race(s)
- exit status 66
复制代码 而要避免这种数据竞态的发生,我们可以限制在同一时间只能有一个 goroutine 访问同一个变量,这种方法称为互斥机制。
我们可以通过缓冲通道和 sync.Mutex 来实现这种互斥锁的操作。
2. 缓冲通道实现互斥锁
我们可以通过容量为 1 的缓冲通道来实现互斥锁的操作,保证同一时间只有一个 goroutine 访问同一个变量,下面是修改后的代码:
[code]var sema = make(chan struct{}, 1)var balance intfunc AddFunc() { sema |