背景
有时候在<font style="color:rgb(68, 68, 68);">Go</font>
代码中可能会存在多个<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。类比现实生活中的例子有十字路口被各个方向的的汽车竞争;还有火车上的卫生间被车厢里的人竞争。
举个例子:
上面的代码中我们开启了两个
var x int64
var wg sync.WaitGroup
func add() {
for i := 0; i < 5000; i++ {
x = x + 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
去累加变量 <font style="color:rgb(68, 68, 68);">x</font>
的值,这两个<font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">goroutine</font>
在访问和修改 <font style="color:rgb(68, 68, 68);background-color:rgb(248, 248, 248);">x</font>
变量的时候就会存在数据竞争,导致最后的结果与期待的不符。
互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine
可以访问共享资源。Go
语言中使用sync
包的Mutex
类型来实现互斥锁。 使用互斥锁来修复上面代码的问题:
sync.WaitGroup
在代码中生硬的使用time.Sleep
肯定是不合适的,Go语言中可以使用sync.WaitGroup来实现并发任务的同步。 sync.WaitGroup有以下几个方法:
方法名 | 功能 |
---|---|
(wg * WaitGroup) Add(delta int) | 计数器+delta |
(wg *WaitGroup) Done() | 计数器-1 |
(wg *WaitGroup) Wait() | 阻塞直到计数器变为0 |
sync.WaitGroup
内部维护着一个计数器,计数器的值可以增加和减少。例如当我们启动了N 个并发任务时,就将计数器值增加N。每个任务完成时通过调用Done()方法将计数器减1。通过调用Wait()来等待并发任务执行完,当计数器值为0时,表示所有并发任务已经完成。
我们利用sync.WaitGroup将上面的代码优化一下
var wg sync.WaitGroup
func hello() {
defer wg.Done()
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1)
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
wg.Wait()
}
需要注意sync.WaitGroup
是一个结构体,传递的时候要传递指针。