这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
数据竞争
数据竞争:当两个或多个协程同时访问同一个内存地址,并且至少有一个是在写时,就会发生数据竞争
下面的实例表现了什么是数据竞争:
package main
import (
"fmt"
"sync"
)
//多个goroutine并发操作全局变量x
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
)
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)
}
输出结果:
在上述程序中开启了两个goroutine对全局变量x进行+1操作,两个 goroutine 在访问和修改全局的
x变量时就会存在数据竞争,某个 goroutine 中对全局变量x的修改可能会覆盖掉另一个 goroutine 中的操作,所以导致最后的结果与预期不符。
互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个 goroutine 可以访问共享资源。Go 语言中使用sync包中提供的Mutex类型来实现互斥锁。
sync.Mutex提供了两个方法供我们使用。
| 方法名 | 功能 |
|---|---|
| func (m *Mutex) Lock() | 获取互斥锁 |
| func (m *Mutex) Unlock() | 释放互斥锁 |
下面的代码使用互斥锁修复了上面代码的问题:
package main
import (
"fmt"
"sync"
)
//多个goroutine并发操作全局变量x
var (
x int64
wg sync.WaitGroup
lock sync.Mutex
)
func add() {
for i := 0; i < 5000; i++ {
lock.Lock()
x = x + 1
lock.Unlock()
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
使用互斥锁能够保证同一时间有且只有一个 goroutine 进入临界区,其他的 goroutine 则在等待锁;当互斥锁释放后,等待的 goroutine 才可以获取锁进入临界区,多个 goroutine 同时等待一个锁时,唤醒的策略是随机的。
读写互斥锁
RWMutex 相对友好些,是经典的单写多读模型。在读锁占用的情况下,会阻止写,但不阻止读,也就是多个 goroutine 可同时获取读锁(调用 RLock() 方法;而写锁(调用 Lock() 方法)会阻止任何其他 goroutine(无论读和写)进来,整个锁相当于由该 goroutine 独占。从 RWMutex 的实现看,RWMutex 类型其实组合了 Mutex。
sync.RWMutex提供了以下5个方法。
| 方法名 | 功能 |
|---|---|
| func (rw *RWMutex) Lock() | 获取写锁 |
| func (rw *RWMutex) Unlock() | 释放写锁 |
| func (rw *RWMutex) RLock() | 获取读锁 |
| func (rw *RWMutex) RUnlock() | 释放读锁 |
| func (rw *RWMutex) RLocker() Locker | 返回一个实现Locker接口的读写锁 |