有时候会存在多个goroutine同时操作一个资源的情况,就会发生竞态问题
例如:
var (
x int64
wg sync.WaitGroup
)
// add 对全局变量x执行5000次加1操作
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分别同时执行add函数,因为是同时在执行所以两个goroutine在访问和修改全局变量x时会存在竞争,一个goroutine执行的结果会覆盖另一个的
1、互斥锁
能够保证同一时间只有一个 goroutine 可以访问共享资源。Go 语言中使用sync包中提供的Mutex类型来实现互斥锁,sync.Mutex提供了两个方法
var (
x int64
wg sync.WaitGroup
m sync.Mutex //定义互斥锁
)
// add 对全局变量x执行5000次加1操作
func add() {
for i := 0; i < 5000; i++ {
m.Lock() //修改x前加锁
x = x + 1
m.Unlock() //修改完解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
使用互斥锁能保证同一时间只有一个goroutine进入资源操作区,其它的goroutine在等待,互斥锁释放后其它的goroutine才可以进入操作
2、读写互斥锁
互斥锁是完全互斥的,但很多场景是读多写少,但并发的去读取资源且不涉及修改时是没必要加锁的。读写锁在 Go 语言中使用sync包中的RWMutex类型
- 读写锁分为读锁和写锁
- 当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待
- 当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待
var{
x int
wg sync.WaitGroup
lock sync.Mutex
rwlock sync.RWMutex
}
func write() {
// lock.Lock() // 加互斥锁
rwlock.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 释放写锁
// lock.Unlock() // 释放互斥锁
wg.Done()
}
func read() {
// lock.Lock() // 加互斥锁
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 释放读锁
// lock.Unlock() // 释放互斥锁
wg.Done()
}
func main() {
start := time.Now()
//多个goroutine去执行write函数,进行写操作
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
//多个goroutine去执行read函数,进行读操作
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来
3、sync.WaitGroup
使用方法和介绍在“goroutine并发”文章中
4、sync.Once
在某些场景下我们需要确保某些操作即使在高并发的场景下也只会被执行一次,例如只加载一次配置文件等,sync包中提供了一个针对只执行一次场景的解决方案——sync.Once
func (o *Once) Do(f func())
如果要执行的函数f需要传递参数就需要搭配闭包来使用
5、sync.Map
Go 语言中内置的 map 不是并发安全的,我们不能在多个 goroutine 中并发对内置的 map 进行读写操作,否则会存在数据竞争问题。
Go语言的sync包中提供了一个开箱即用的并发安全版 map——sync.Map。开箱即用表示其不用像内置的 map 一样使用 make 函数初始化就能直接使用
//定义并发安全的map
var m = sync.Map{}
func main(){
//sync.WaitGroup是结构体
wg := sync.WaitGroup{}
//对m执行20个并发的读写操作
for i := 0;i<20;i++{
wg.Add(1)
//多个goroutine操作map
go func(n int){
key := strconv.Itoa(n)
m.Store(key, n) //存储键值
value,_:=m.Load(key) //根据key取值
fmt.Printf("k=:%v,v:=%v\n", key, value)
wg.Done()
}(i)
}
wg.Wait()
}