0/参考网址
zhuanlan.zhihu.com/p/105994357
1/并发安全和锁
在某些场景中,可能会存在多个goroutine(协程)同时操作一个资源,这种情况会发生竞态问题,举个例子
package main
import (
"fmt"
"sync"
)
// 多个goroutine并发操作全局变量x
var x int64
var wg sync.WaitGroup
func add() {
for i := 0; i < 50; i++ {
x = x + 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
以上代码中,两个goroutine去累加变量x,这两个goroutine在访问和修改x变量的时候就会存在数据竞争。因此就需要锁。
1.1/互斥锁
互斥锁是一种常用的控制共享资源访问的方法。
它能够保证同时(同一时间)只有一个goroutine可以访问共享资源。
Go语言中通过sync包中的Mutex类型来实现互锁。
sync.Mutex是一个结构体,值类型,给函数传参的时候需要传指针。
var (
x int64
wg sync.WaitGroup
lock sync.Mutex // 互斥锁
)
// 修改了上面的func add()函数
func add() {
for i := 0; i < 50; i++ {
lock.Lock() // 加锁,这就相当于,此时此刻,该变量x只能自己使用,别人不能操作
x = x + 1
lock.Unlock() // 释放锁
}
wg.Done()
}
使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待,
如果有多个goroutine等待,下一次将在这些goroutine中随机选择一个进入临界区。
1.2/读写互斥锁
互斥锁是完全互斥的,但是很多场景下是读多写少。
如果是并发地去读一个资源不涉及修改(写)的时候是没有必要加锁的。
读写锁在Go语言中使用sync包中的RWMutex类型。
package main
import (
"fmt"
"sync"
"time"
)
// 读写互斥锁
var (
x int64
wg sync.WaitGroup
lock sync.Mutex // 互斥锁
rwLock sync.RWMutex // 读写互斥锁
)
// 函数,读变量
func read() {
// lock.Lock()
rwLock.Lock()
time.Sleep(time.Millisecond)
// lock.Unlock()
rwLock.Unlock()
wg.Done()
}
// 函数,写变量
func write() {
// lock.Lock()
rwLock.Lock()
x = x + 1
time.Sleep(time.Millisecond * 10)
// lock.Unlock()
rwLock.Unlock()
wg.Done()
}
func main() {
start := time.Now()
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
wg.Wait()
fmt.Println(time.Now().Sub(start))
}
需要注意的是读写锁非常适合读多写少的场景,如果读写的操作差别不大,读写锁的优势就发挥不出来了。
|