简述Golang中的锁机制主要包含互斥锁和读写锁
互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段 。在Go中主要使用 sync.Mutex的结构体表示 。
一个简单的示例:
func mutex() { var mu sync.Mutex mu.Lock() fmt.Println("locked") mu.Unlock()}或者也可以使用defer来实现,这在整个函数流程中全部要加锁时特别有用,还有一个好处就是可以防止忘记Unlock
func mutex() { var mu sync.Mutex mu.Lock() defer mu.Unlock() fmt.Println("locked")}互斥锁是开箱即用的,只需要申明sync.Mutex即可直接使用
var mu sync.Mutex互斥锁应该是成对出现,在同步语句不可以再对锁加锁,看下面的示例:
func mutex() { var mu sync.Mutex mu.Lock() fmt.Println("parent locked") mu.Lock() fmt.Println("sub locked") mu.Unlock() mu.Unlock()}此时则会出现fatal error: all goroutines are asleep - deadlock!错误
同样,如果多次对一个锁解锁,则会出现fatal error: sync: unlock of unlocked mutex错误
func mutex() { var mu sync.Mutex mu.Lock() fmt.Println("locked") mu.Unlock() mu.Unlock()}那么在goroutine中是否对外部锁加锁呢?
func mutex() { var mu sync.Mutex fmt.Println("parent lock start") mu.Lock() fmt.Println("parent locked") for i := 0; i <= 2; i++ { go func(i int) { fmt.Printf("sub(%d) lock startn", i) mu.Lock() fmt.Printf("sub(%d) lockedn", i) time.Sleep(time.Microsecond * 30) mu.Unlock() fmt.Printf("sub(%d) unlockn", i) }(i) } time.Sleep(time.Second * 2) mu.Unlock() fmt.Println("parent unlock") time.Sleep(time.Second * 2)}先看上面的函数执行结果
parent lock startparent lockedsub(0) lock startsub(2) lock startsub(1) lock startparent unlock // 必须等到父级先解锁,后面则会阻塞sub(0) locked // 解锁后子goroutine才能执行锁定sub(0) unlocksub(2) lockedsub(2) unlocksub(1) lockedsub(1) unlock为了方便调试,使用了time.Sleep()来延迟保证goroutine的执行 从结果中可以看出,当所有的goroutine遇到Lock时都会阻塞,而当main函数中的Unlock执行后,会有一个优先(无序)的goroutine来占得锁,其它的则再次进入阻塞状态 。
总结:
- 互斥锁必须成对出现
- 同级别互斥锁不能嵌套使用
- 父级中如果存在锁,当在goroutine中执行重复锁定操作时goroutine将被阻塞,直到原互斥锁解锁,多个goroutine将会争抢当前锁资源,其它继续阻塞 。
文章插图
读写锁读写锁和互斥锁不同之处在于,可以分别针对读操作和写操作进行分别锁定,这样对于性能有一定的提升 。读写锁,对于多个写操作,以及写操作和读操作之前都是互斥的这一点基本等同于互斥锁 。但是对于同时多个读操作之前却非互斥关系,这也是相读写锁性能高于互斥锁的主要原因 。
读写锁也是开箱即用型的
var rwm = sync.RWMutex读写锁分为写锁和读锁:
- 写锁定和写解锁
- 读锁定和读解锁
func rwMutex() { var rwm sync.RWMutex rwm.Lock() fmt.Println("locked") rwm.RUnlock()}fatal error: sync: RUnlock of unlocked RWMutex
对于读写锁,同一资源可以同时有多个读锁定,如:
func rwMutex() { var rwm sync.RWMutex rwm.RLock() rwm.RLock() rwm.RLock() fmt.Println("locked") rwm.RUnlock() rwm.RUnlock() rwm.RUnlock()}但对于写锁定只能有一个(和互斥锁相同),同时使用多个会产生deadlock的panic,如:
func rwMutex() { var rwm sync.RWMutex rwm.Lock() rwm.Lock() rwm.Lock() fmt.Println("locked") rwm.Unlock() rwm.Unlock() rwm.Unlock()}在goroutine中,写解锁会试图唤醒所有想要进行读锁定而被阻塞的goroutine 。
而读解锁会在已无任何读锁定的情况下,试图唤醒一个想进行写锁定而被阻塞的goroutine 。
下面看一个完整示例:
func rwMutex() { var rwm sync.RWMutex for i := 0; i <= 2; i++ { go func(i int) { fmt.Printf("go(%d) start lockn", i) rwm.RLock() fmt.Printf("go(%d) lockedn", i) time.Sleep(time.Second * 2) rwm.RUnlock() fmt.Printf("go(%d) unlockn", i) }(i) } // 先sleep一小会,保证for的goroutine都会执行 time.Sleep(time.Microsecond * 100) fmt.Println("main start lock") // 当子进程都执行时,且子进程所有的资源都已经Unlock了 // 父进程才会执行 rwm.Lock() fmt.Println("main locked") time.Sleep(time.Second) rwm.Unlock()}go(0) start lockgo(0) lockedgo(1) start lockgo(1) lockedgo(2) start lockgo(2) lockedmain start lockgo(2) unlockgo(0) unlockgo(1) unlockmain locked
推荐阅读
- 每日正能量语录:善良的心,就是太阳 善良的心就是太阳
- 有龙字的成语龙凤呈祥 有龙字的成语
- 梦见着火了自己去灭火梦见女同学向我问一下英语问题 梦见着火了自己去灭火还担心别人误会
- 语文书灵异事件照片 四年级语文书上的诡异照片
- 深山含笑花语 深山含笑介绍
- Python打牢基础,从12个语法开始
- 开天眼念什么咒语 开天眼咒开天眼咒语
- 英语写作万能模板 英语万能作文模板
- 不足为外人道成语故事 不足为外人道也
- 平安夜快乐英文怎么说 平安夜快乐的英语