1.概念
Golang中的Mutex是互斥锁,为了保护并发访问导致的意想不到的结果。
2.基本用法
Lock方法: 获取锁
Unlock方法: 释放锁
3. 错误示例
package main
import (
"fmt"
"sync"
)
type Counter struct {
count uint64
}
func main() {
var counter Counter
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
counter.count++
}
}()
}
wg.Wait()
fmt.Println(counter.count)
}
上述代码中的输出结果并没有准确的输出100000,原因就是因为在多个goroutine并发执行时,并不会按照顺序执行,因此在争夺count的资源,导致在某一个goroutine的计算结果为200时,被另外一个goroutine争夺资源计算结果更改为100
4. 使用互斥锁Mutex解决
package main
import (
"fmt"
"sync"
)
type Counter struct {
count uint64
mu sync.Mutex
}
func (c *Counter) Incr() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
var counter Counter
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
counter.Incr()
}
}()
}
wg.Wait()
fmt.Println(counter.Count())
}
上述代码则能准确的输出100000
5. 锁的争夺模式
正常模式
优势:拥有更好的性能
正常模式下, 锁的争取是遵循先入先出队列,被唤醒的goroutine不会直接获得锁,会与新进来的goroutine进行竞争。新来的goroutine有先天的优势,因为它们正在CPU中运行,可能它们的数量还不少,所以,在高并发情况下,被唤醒的 waiter 可能比较悲剧地获取不到锁,这时就需要饥饿模式
饥饿模式
饥饿模式是对公平性和性能的一种平衡,它避免了某些 goroutine 长时间的等待锁
在饥饿模式下,Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁,即使看起来锁没有被持有,它也不会去抢,它会乖乖地加入到等待队列的尾部
6. 模式转换条件
正常模式转为饥饿模式条件:
1.如果获取锁等待的时间超过阈值1毫秒,那么mutex会进入饥饿模式
饥饿模式转为正常模式条件:
1.当前获取锁的goroutine是队列中的最后一个
2.当前获取锁的goroutine等待的时间小于阈值1毫秒