背景
有时候在<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 int64var wg sync.WaitGroupfunc 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.WaitGroupfunc 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是一个结构体,传递的时候要传递指针。
