我正在参加「掘金·启航计划」
有时候go代码中可能会存在多个goroutine同时操作一个资源,这种情况会发生竞态问题,类比火车上卫生间被车厢的人竞争。
例:
var wg sync.WaitGroup
var x int = 0
func hello() {
for i := 0; i < 5000; i++ {
x += 1
}
wg.Done()
}
func main() {
wg.Add(2)
go hello()
go hello()
wg.Wait()
fmt.Println(x)//输出可能不等于10000
}
互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个gotoutine可以访问共享资源,go语言使用sync包的Mutex类型来实现互斥锁,使用互斥锁能保证同一时间有且只有一个gotoutine进入临界区,其他的gotoutine则在等待锁。
//sync.Mutex 互斥锁
var wg sync.WaitGroup
var lock sync.Mutex
var x int = 0
func hello() {
for i := 0; i < 5000; i++ {
lock.Lock() //加锁
x += 1
lock.Unlock()//解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go hello()
go hello()
wg.Wait()
fmt.Println(x)//正常输入10000
}
读写互斥锁
互斥锁是完全互斥的,但是很多实际的场景下是读多写少的,我们并发读的时候不涉及资源修改的时候没必要枷锁,这种场景下使用读写锁是更好的一种选择,读写锁在go语言中使用sync包中的RWMutex类型
读写锁分为两种:读锁和写锁,当一个goroutine获取读锁之后,其他的gotoutine如果是获取读锁会继续获得锁,如果获取写锁就会等到,当一个goroutine获取写锁之后,其他的gotoutine无论是获取读锁还是写锁都会等待。
需要注意的是读写锁适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势不明显
示例:
var (
wg sync.WaitGroup
x int64 = 0
lock sync.Mutex
rwlock sync.RWMutex
)
func read() {
defer wg.Done()
//lock.Lock() //加互斥锁
rwlock.RLock() //加读锁
time.Sleep(10 * time.Millisecond)
//lock.Unlock()//解互斥锁
rwlock.RUnlock() //解读锁
}
func write() {
defer wg.Done()
//lock.Lock()//加互斥锁
rwlock.Lock() //加写锁
x++
//lock.Unlock()//解互斥锁
rwlock.Unlock()
}
func main() {
now := time.Now()
for i := 10; i < 10; i++ {
wg.Add(1)
go write()
}
for i := 0; i <= 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
fmt.Println(time.Now().Sub(now)) //互斥锁耗时11.3s 读写锁耗时20.9ms
}