新聞中心
我在極客時(shí)間上開(kāi)了一門(mén)面向中高級(jí)Go程序員的課程:Go 并發(fā)編程實(shí)戰(zhàn)課,有讀者問(wèn)Go channel中的實(shí)現(xiàn)中使用了mutex,這個(gè)mutex和標(biāo)準(zhǔn)庫(kù)中的Mutex有什么不同?正好在百度廠(chǎng)內(nèi)分享Go相關(guān)課程中有同事也提出了相同的問(wèn)題,所以我專(zhuān)門(mén)寫(xiě)一篇文章介紹一下。

成都創(chuàng)新互聯(lián)于2013年開(kāi)始,先為忻城等服務(wù)建站,忻城等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢(xún)服務(wù)。為忻城企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問(wèn)題。
sync.Mutex是一個(gè)high level的同步原語(yǔ),是為廣大的Go開(kāi)發(fā)者開(kāi)發(fā)應(yīng)用程序提供的一種數(shù)據(jù)結(jié)構(gòu),現(xiàn)在它的內(nèi)部實(shí)現(xiàn)邏輯比較復(fù)雜了,包含spin和饑餓處理等邏輯,它底層使用了運(yùn)行時(shí)的low level的一些函數(shù)和atomic的一些方法。
而運(yùn)行時(shí)中的mutex是為運(yùn)行時(shí)內(nèi)部使用互斥鎖而提供的一個(gè)同步原語(yǔ),它提供了spin和等待隊(duì)列,并沒(méi)有去解決饑餓狀態(tài),而且它的實(shí)現(xiàn)和sync.Mutex的實(shí)現(xiàn)也是不一樣的。它并沒(méi)有以方法的方式提供Lock/Unlock,而是提供lock/unlock函數(shù)實(shí)現(xiàn)請(qǐng)求鎖和釋放鎖。
Dan Scales 今年年初的時(shí)候又為運(yùn)行時(shí)的鎖增加了static locking rank的功能。他為運(yùn)行時(shí)的架構(gòu)無(wú)關(guān)的鎖( architecture-independent locks)定義了rank,并且又定義了一些運(yùn)行時(shí)的鎖的偏序(此鎖之前允許持有哪些鎖)。這是運(yùn)行時(shí)鎖的一個(gè)巨大改變,但是很遺憾并沒(méi)有一篇設(shè)計(jì)文檔詳細(xì)去描述這個(gè)功能的設(shè)計(jì),你可以通過(guò)提交的comment(#0a820007)和代碼中的注釋去了解runtime內(nèi)部鎖的代碼變化。
本質(zhì)上來(lái)說(shuō),這個(gè)功能用來(lái)檢查鎖的順序是不是按照文檔設(shè)計(jì)的順序執(zhí)行的,如果有違反設(shè)定的順序,就有可能死鎖發(fā)生。因?yàn)槿狈?zhǔn)確的文檔說(shuō)明,并且這個(gè)功能主要是用來(lái)檢查運(yùn)行時(shí)鎖的執(zhí)行順序的,所以在本文中我把這一段邏輯抹去不介紹了。實(shí)際Go運(yùn)行時(shí)要開(kāi)始這個(gè)檢查的話(huà),你需要設(shè)置變量GOEXPERIMENT=staticlockranking。
那么接下來(lái)我們看看運(yùn)行時(shí)的mutex的數(shù)據(jù)結(jié)構(gòu)的定義以及l(fā)ock/unlock的實(shí)現(xiàn)。
運(yùn)行時(shí)mutex數(shù)據(jù)結(jié)構(gòu)
運(yùn)行時(shí)的mutex數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單,如下所示,定義在runtime2.go中:
- type mutex struct {
- lockRankStruct
- // Futex-based impl treats it as uint32 key,
- // while sema-based impl as M* waitm.
- // Used to be a union, but unions break precise GC.
- key uintptr
- }
如果不啟用lock ranking,其實(shí)lockRankStruct就是一個(gè)空結(jié)構(gòu):
- type lockRankStruct struct {
- }
那么對(duì)于運(yùn)行時(shí)的mutex,最重要的就是key字段了。這個(gè)字段針對(duì)不同的架構(gòu)有不同的含義。
對(duì)于dragonfly、freebsd、linux架構(gòu),mutex會(huì)使用基于Futex的實(shí)現(xiàn), key就是一個(gè)uint32的值。 Linux提供的Futex(Fast user-space mutexes)用來(lái)構(gòu)建用戶(hù)空間的鎖和信號(hào)量。Go 運(yùn)行時(shí)封裝了兩個(gè)方法,用來(lái)sleep和喚醒當(dāng)前線(xiàn)程:
- futexsleep(addr uint32, val uint32, ns int64):原子操作`if addr == val { sleep }`。
- futexwakeup(addr *uint32, cnt uint32):?jiǎn)拘训刂穉ddr上的線(xiàn)程最多cnt次。
對(duì)于其他的架構(gòu),比如aix、darwin、netbsd、openbsd、plan9、solaris、windows,mutex會(huì)使用基于sema的實(shí)現(xiàn),key就是M* waitm。Go 運(yùn)行時(shí)封裝了三個(gè)方法,用來(lái)創(chuàng)建信號(hào)量和sleep/wakeup:
- func semacreate(mp *m):創(chuàng)建信號(hào)量
- func semasleep(ns int64) int32: 請(qǐng)求信號(hào)量,請(qǐng)求不到會(huì)休眠一段時(shí)間
- func semawakeup(mp *m):?jiǎn)拘裮p
基于這兩種實(shí)現(xiàn),分別有不同的lock和unlock方法的實(shí)現(xiàn),主要邏輯都是類(lèi)似的,所以接下來(lái)我們只看基于Futex的lock/unlock。
請(qǐng)求鎖lock
如果不使用lock ranking特性,lock的邏輯主要是由lock2實(shí)現(xiàn)的。
- func lock(l *mutex) {
- lockWithRank(l, getLockRank(l))
- }
- func lockWithRank(l *mutex, rank lockRank) {
- lock2(l)
- }
- func lock2(l *mutex) {
- // 得到g對(duì)象
- gp := getg()
- // g綁定的m對(duì)象的lock計(jì)數(shù)加1
- if gp.m.locks < 0 {
- throw("runtime·lock: lock count")
- }
- gp.m.locks++
- // 如果有幸運(yùn)光環(huán),原來(lái)鎖沒(méi)有被持有,一把就獲取到了鎖,就快速返回了
- v := atomic.Xchg(key32(&l.key), mutex_locked)
- if v == mutex_unlocked {
- return
- }
- // 否則原來(lái)的可能是MUTEX_LOCKED或者M(jìn)UTEX_SLEEPING
- wait := v
- // 單核不進(jìn)行spin,多核CPU情況下會(huì)嘗試spin
- spin := 0
- if ncpu > 1 {
- spin = active_spin
- }
- for {
- // 嘗試spin,如果鎖已經(jīng)釋放,嘗試搶鎖
- for i := 0; i < spin; i++ {
- for l.key == mutex_unlocked {
- if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
- return
- }
- }
- // PAUSE
- procyield(active_spin_cnt)
- }
- // 再?lài)L試搶鎖, rescheduling.
- for i := 0; i < passive_spin; i++ {
- for l.key == mutex_unlocked {
- if atomic.Cas(key32(&l.key), mutex_unlocked, wait) {
- return
- }
- }
- osyield()
- }
- // 再?lài)L試搶鎖,并把key設(shè)置為mutex_sleeping,如果搶鎖成功,返回
- v = atomic.Xchg(key32(&l.key), mutex_sleeping)
- if v == mutex_unlocked {
- return
- }
- // 否則sleep等待
- wait = mutex_sleeping
- futexsleep(key32(&l.key), mutex_sleeping, -1)
- }
- }
unlock
如果不使用lock ranking特性,unlock的邏輯主要是由unlock2實(shí)現(xiàn)的。
- func unlock(l *mutex) {
- unlockWithRank(l)
- }
- func unlockWithRank(l *mutex) {
- unlock2(l)
- }
- func unlock2(l *mutex) {
- // 將key的值設(shè)置為mutex_unlocked
- v := atomic.Xchg(key32(&l.key), mutex_unlocked)
- if v == mutex_unlocked {
- throw("unlock of unlocked lock")
- }
- // 如果原來(lái)有線(xiàn)程在sleep,喚醒它
- if v == mutex_sleeping {
- futexwakeup(key32(&l.key), 1)
- }
- //得到當(dāng)前的goroutine以及和它關(guān)聯(lián)的m,將鎖的計(jì)數(shù)減1
- gp := getg()
- gp.m.locks--
- if gp.m.locks < 0 {
- throw("runtime·unlock: lock count")
- }
- if gp.m.locks == 0 && gp.preempt { // restore the preemption request in case we've cleared it in newstack
- gp.stackguard0 = stackPreempt
- }
- }
總體來(lái)說(shuō),運(yùn)行時(shí)的mutex邏輯還不太復(fù)雜,主要是需要處理不同的架構(gòu)的實(shí)現(xiàn),它休眠喚醒的對(duì)象是m,而sync.Mutex休眠喚醒的對(duì)象是g。
網(wǎng)站題目:Go運(yùn)行時(shí)中的 Mutex
路徑分享:http://fisionsoft.com.cn/article/djdojhd.html


咨詢(xún)
建站咨詢(xún)
