1.互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine
可以访问共享资源。Go语言中使用sync
包的Mutex
类型来实现互斥锁。 使用互斥锁来修复上面代码的问题:
var x int64
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
for i := 0; i < 50000; i++ {
lock.Lock() // 加锁
x = x + 1
lock.Unlock() // 解锁
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
结果永远是10000。 对公共资源操作时,应该加锁
使用互斥锁能够保证同一时间有且只有一个goroutine
进入临界区,其他的goroutine
则在等待锁;当互斥锁释放后,等待的goroutine
才可以获取锁进入临界区,多个goroutine
同时等待一个锁时,唤醒的策略是随机的。
模拟读多写少的一个场景
var (
x = 0
wg sync.WaitGroup
lock sync.Mutex
)
func read () {
defer wg.Done()
lock.Lock()
fmt.Println(x)
time.Sleep(time.Millisecond) 读需要1毫秒
lock.Unlock()
}
func write() {
defer wg.Done()
lock.Lock()
x ++
lock.Unlock()
time.Sleep(time.Millisecond * 5) 写需要5毫秒
}
func main() {
start := time.Now()
for i:= 0; i<10 ; i++ {
go write()
wg.Add(1)
}
for i:= 0; i<1000 ; i++ {
go read()
wg.Add(1)
}
wg.Wait()
fmt.Println(time.Since(start)) 15.516195s
}
互斥锁,在读的时候,也会阻塞下一个读线程,这样效率很低。
2.读写互斥锁
普通锁是 sync.Mutex
读写锁是 sync.RWMutex
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync
包中的RWMutex
类型。
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine
如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine
获取写锁之后,其他的goroutine
无论是获取读锁还是写锁都会等待。
RWMutex提供了四个方法:
func (*RWMutex) Lock // 写锁定
func (*RWMutex) Unlock // 写解锁
func (*RWMutex) RLock // 读锁定
func (*RWMutex) RUnlock // 读解锁
读写锁示例:
var (
x = 0
wg sync.WaitGroup
lock sync.RWMutex
)
func read () {
defer wg.Done()
lock.RLock() 加读锁
fmt.Println(x)
time.Sleep(time.Millisecond)
lock.RUnlock() 解读锁
}
func write() {
defer wg.Done() 加写锁
lock.Lock()
x ++
lock.Unlock() 解写锁
time.Sleep(time.Millisecond * 5)
}
func main() {
start := time.Now()
for i:= 0; i<10 ; i++ {
go write()
wg.Add(1)
}
for i:= 0; i<1000 ; i++ {
go read()
wg.Add(1)
}
wg.Wait()
fmt.Println(time.Since(start)) 20.0658ms,速度和互斥锁差别非常大
}
需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。