本文共 3821 字,大约阅读时间需要 12 分钟。
Mutex是互斥锁的意思,也叫排他锁,同一时刻一段代码只能被一个线程运行,使用只需要关注方法Lock(加锁)和Unlock(解锁)即可。
在Lock()和Unlock()之间的代码段称为资源的临界区(critical section),是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码。先来一段不加群的代码,10个协程同时累加1万
package mainimport ( "fmt" "sync")func main() { var count = 0 var wg sync.WaitGroup //十个协程数量 n := 10 wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() //1万叠加 for j := 0; j < 10000; j++ { count++ } }() } wg.Wait() fmt.Println(count)}
运行结果如下
38532
正确的结果应该是100000,这里出现了并发写入更新错误的情况
我们再添加锁,代码如下
package mainimport ( "fmt" "sync")func main() { var count = 0 var wg sync.WaitGroup var mu sync.Mutex //十个协程数量 n := 10 wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() //1万叠加 for j := 0; j < 10000; j++ { mu.Lock() count++ mu.Unlock() } }() } wg.Wait() fmt.Println(count)}
运行结果如下,可以看到,已经看到结果变成了正确的100000
Mutex在大量并发的情况下,会造成锁等待,对性能的影响比较大。
如果某个读操作的协程加了锁,其他的协程没必要处于等待状态,可以并发地访问共享变量,这样能让读操作并行,提高读性能。RWLock就是用来干这个的,这种锁在某一时刻能由什么问题数量的reader持有,或者被一个wrtier持有主要遵循以下规则 :
Go语言的读写锁方法主要有下面这种
package mainimport ( "fmt" "sync" "time")func main() { var m sync.RWMutex go read(&m, 1) go read(&m, 2) go read(&m, 3) time.Sleep(2 * time.Second)}func read(m *sync.RWMutex, i int) { fmt.Println(i, "reader start") m.RLock() fmt.Println(i, "reading") time.Sleep(1 * time.Second) m.RUnlock() fmt.Println(i, "reader over")}
运行如下
可以看到,3的读还没结束,1和2已经开始读了
package mainimport ( "fmt" "sync" "time")var count = 0func main() { var m sync.RWMutex for i := 1; i <= 3; i++ { go write(&m, i) } for i := 1; i <= 3; i++ { go read(&m, i) } time.Sleep(1 * time.Second) fmt.Println("final count:", count)}func read(m *sync.RWMutex, i int) { fmt.Println(i, "reader start") m.RLock() fmt.Println(i, "reading count:", count) time.Sleep(1 * time.Millisecond) m.RUnlock() fmt.Println(i, "reader over")}func write(m *sync.RWMutex, i int) { fmt.Println(i, "writer start") m.Lock() count++ fmt.Println(i, "writing count", count) time.Sleep(1 * time.Millisecond) m.Unlock() fmt.Println(i, "writer over")}
运行结果如下
如果我们可以明确区分reader和writer的协程场景,且是大师的并发读、少量的并发写,有强烈的性能需要,我们就可以考虑使用读写锁RWMutex替换Mutex
当两个或两个以上的进程在执行过程中,因争夺资源而处理一种互相等待的状态,如果没有外部干涉无法继续下去,这时我们称系统处于死锁或产生了死锁。
死锁主要有以下几种场景。没有成对出现容易会出现死锁的情况,或者是Unlock 一个未加锁的Mutex而导致 panic,代码建议以下面紧凑的方式出现
mu.Lock()defer mu.Unlock()
package mainimport ( "fmt" "sync")func main() { var mu sync.Mutex mu.Lock() defer mu.Unlock() copyTest(mu)}//这里复制了一个锁,造成了死锁func copyTest(mu sync.Mutex) { mu.Lock() defer mu.Unlock() fmt.Println("ok")}
在函数外层已经加了一个Lock,在拷贝的时候又执行了一次Lock,因此这是一个永远不会获得的锁,因为外层函数的Unlock无法执行。
A等待B,B等待C,C等待A,陷入了无限循环(哲学家就餐问题)
package mainimport ( "sync")func main() { var muA, muB sync.Mutex var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() muA.Lock() defer muA.Unlock() //A依赖B muB.Lock() defer muB.Lock() }() go func() { defer wg.Done() muB.Lock() defer muB.Lock() //B依赖A muA.Lock() defer muA.Unlock() }() wg.Wait()}
以上就是Go语言的锁使用,由chenqionghe倾情整理,giao~
转载地址:http://qzlkz.baihongyu.com/