日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Go语言 读写锁互斥锁原理剖析(2)

發(fā)布時間:2024/1/17 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go语言 读写锁互斥锁原理剖析(2) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

互斥鎖百科)定義:“在編程中,引入了對象互斥鎖的概念,來保證共享數(shù)據(jù)操作的完整性。每個對象都對應(yīng)于一個可稱為" 互斥鎖" 的標(biāo)記,這個標(biāo)記用來保證在任一時刻,只能有一個線程訪問該對象。”,顧名思義就是互相排斥的鎖了。

當(dāng)程序中就一個協(xié)程時,不需要加鎖,但是實際工程中不會只有單協(xié)程,可能有很多協(xié)程同時訪問公共資源,所以這個時候就需要用到鎖,互斥鎖的使用場景一般有:

  • 多個協(xié)程同時讀相同的數(shù)據(jù)時
  • 多個協(xié)程同時寫相同的數(shù)據(jù)時
  • 同一個資源,同時有讀和寫操作時
  • 讀寫鎖之后,我們繼續(xù)來說說互斥鎖,互斥鎖從原理上來說要比讀寫鎖復(fù)雜一些,在Go語言中提供了sync.Mutex標(biāo)準(zhǔn)庫,Mutex結(jié)構(gòu)體來定義。Mutex同樣繼承于Locker接口。

    互斥鎖特點:一次只能一個協(xié)程擁有互斥鎖,其他線程只有等待。

    源碼基于:go version go1.13.4 windows/amd64,sync包中Mutex、RWMutex的方法的inline化帶來的性能提升,官方說法是10%。

    兩種操作模式:

  • 正常模式:所有協(xié)程以先進(jìn)先出(FIFO)方式進(jìn)行排隊,被喚醒的協(xié)程同樣需要競爭方式爭奪鎖,新協(xié)程爭搶會有優(yōu)勢,因為他們已經(jīng)運行在CPU上,更容易搶到鎖,如果一個協(xié)程在等待超過1毫秒會自動切換到饑餓模式下。
  • 饑餓模式:互斥鎖會直接由解鎖的協(xié)程交給隊列頭部的等待者,新爭搶者不能直接獲得鎖,不嘗試自旋,會老老實實的等。
  • 兩種工作模式:

  • 競爭模式:所有協(xié)程一起搶
  • 隊列模式:所有協(xié)程一起排隊
  • 這兩種工作模式會通過一些情況進(jìn)行切換的。

    互斥鎖的定義

    type Mutex struct {state int32 // 互斥鎖上鎖狀態(tài)sema uint32 // 信號量 }

    state=0時是未上鎖,state=1時是鎖定狀態(tài)。

    互斥鎖常量的定義

    const (mutexLocked = 1 << iota // 十進(jìn)制:1,二進(jìn)制:0001mutexWoken // 十進(jìn)制:2,二進(jìn)制:0010mutexStarving // 十進(jìn)制:4,二進(jìn)制:0100mutexWaiterShift = iota // 十進(jìn)制:3,二進(jìn)制:0011starvationThresholdNs = 1e6 // 1e+06 )

    看一下互斥鎖的結(jié)構(gòu)主要方法,主要有Lock()和Unlonk()方法組成,使用Lock()加鎖后便不能再次對其加鎖操作,直到Unlock()解鎖后才能再次加鎖,適用于讀寫不確定的場景,并且只允許只有一個讀或者寫的場景。

    Lock()

    func (m *Mutex) Lock() {// ①CAS嘗試獲取鎖,state為0表示沒有協(xié)程持有鎖,直接獲得鎖,將mutexLocked置為1。// 如果設(shè)置成功,直接返回。如果獲取鎖失敗會進(jìn)入lockSlow方法進(jìn)行自旋搶鎖,直到搶到鎖后返回。if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer(m))}return}m.lockSlow() }

    首先、嘗試CAS獲取鎖,這里直接調(diào)用CompareAndSwapInt32方法來原子操作檢測鎖的狀態(tài),可以加鎖會將狀態(tài)轉(zhuǎn)為1,不可以加鎖則狀態(tài)為0。state為0表示沒有協(xié)程持有鎖,這個時候直接獲得鎖并將mutexLocked設(shè)置成1。如果設(shè)置成功了直接返回。如果獲取鎖失敗了會進(jìn)入lockSlow方法進(jìn)行自旋搶鎖,直到搶到鎖為止。

    lockSlow()

    func (m *Mutex) lockSlow() {var waitStartTime int64 // 協(xié)程等待時間starving := false // 鎖的模式awoke := false // 循環(huán)標(biāo)記iter := 0 // 計數(shù)器old := m.state // 當(dāng)前的鎖狀態(tài)for {// 1、old&0101==0001等于1說明已經(jīng)加過鎖,(old第1位一定是1,第3位一定是0)這時候未處于饑餓模式,開始自旋。if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {// 2、當(dāng)前協(xié)程未能成功更新mutexWoken位,mutexWoken位仍然為0,等待隊列為空,更新mutexWoken成功開始自旋。if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {awoke = true}// 3、將當(dāng)前的協(xié)程標(biāo)識為喚醒狀態(tài)后,執(zhí)行自旋操作,計數(shù)器+1,當(dāng)前狀態(tài)更新到old。runtime_doSpin()iter++old = m.statecontinue}new := old// 4、新到的協(xié)程第三位等于0為正常模式需要排除if old&mutexStarving == 0 {new |= mutexLocked}// 5、當(dāng)old的1和3位為1時,為饑餓模式,需要去排隊if old&(mutexLocked|mutexStarving) != 0 {new += 1 << mutexWaiterShift}// 6、切換到饑餓模式,解鎖時不切換if starving && old&mutexLocked != 0 {new |= mutexStarving}// 7、喚醒if awoke {// 8、互斥狀態(tài)不相同就panicif new&mutexWoken == 0 {throw("sync: inconsistent mutex state")}// 同時把awoke位清掉new &^= mutexWoken}if atomic.CompareAndSwapInt32(&m.state, old, new) {// 9、old的第1位和第3位一定不是1,未鎖定而且處于饑餓模式。獲取鎖成功if old&(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// 10、被喚醒的協(xié)程搶鎖失敗,重新放到隊列首部queueLifo := waitStartTime != 0if waitStartTime == 0 {waitStartTime = runtime_nanotime()}// 11、進(jìn)入休眠狀態(tài),等待信號喚醒runtime_SemacquireMutex(&m.sema, queueLifo, 1)// 確認(rèn)當(dāng)前的鎖的狀態(tài)starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNsold = m.stateif old&mutexStarving != 0 {// 12、饑餓模式不會出現(xiàn)mutex被鎖住|喚醒,等待隊列不能為0if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}// 13、拿到鎖,等待數(shù)-1delta := int32(mutexLocked - 1<<mutexWaiterShift)// 非饑餓模式,等待者只有一個時,退出饑餓模式if !starving || old>>mutexWaiterShift == 1 {delta -= mutexStarving}// 14、更新狀態(tài),高位原子計數(shù),直接添加atomic.AddInt32(&m.state, delta)break}// 15、awoke=true,不處于饑餓模式,新到達(dá)的協(xié)程先獲得鎖awoke = trueiter = 0} else {// 16、old = m.state,自旋沒成功,更新new,記錄當(dāng)前的狀態(tài)old = m.state}}if race.Enabled {race.Acquire(unsafe.Pointer(m))} }

    lockSlow方法中
    首先設(shè)置循環(huán)標(biāo)記awoke,初始化計數(shù)器,將當(dāng)前鎖的狀態(tài)賦值給old等參數(shù)后進(jìn)入循環(huán),
    1、old&0101==0001等于1說明已經(jīng)加過鎖,(old第1位一定是1,第3位一定是0)這時候未處于饑餓模式,開始自旋。
    2、當(dāng)前協(xié)程未能成功更新mutexWoken位,mutexWoken位仍然為0,等待隊列為空,更新mutexWoken成功開始自旋。
    3、將當(dāng)前的協(xié)程標(biāo)識為喚醒狀態(tài)后,執(zhí)行自旋操作,計數(shù)器+1,當(dāng)前狀態(tài)更新到old。
    4、新到的協(xié)程第三位等于0為正常模式需要排除
    5、當(dāng)old的1和3位為1時,為饑餓模式,需要去排隊
    6、切換到饑餓模式,解鎖時不切換
    7、喚醒
    8、互斥狀態(tài)不相同就panic,同時把awoke位清掉
    9、old的第1位和第3位一定不是1,未鎖定而且處于饑餓模式。獲取鎖成功
    10、被喚醒的協(xié)程搶鎖失敗,重新放到隊列首部
    11、進(jìn)入休眠狀態(tài),等待信號喚醒,確認(rèn)當(dāng)前的鎖的狀態(tài)
    12、饑餓模式不會出現(xiàn)mutex被鎖住|喚醒,等待隊列不能為0
    13、拿到鎖,等待數(shù)-1。非饑餓模式,等待者只有一個時,退出饑餓模式
    14、更新狀態(tài),高位原子計數(shù),直接添加
    15、awoke=true,不處于饑餓模式,新到達(dá)的協(xié)程先獲得鎖
    16、old = m.state,自旋沒成功,更新new,記錄當(dāng)前的狀態(tài)

    Unlock()

    func (m *Mutex) Unlock() {if race.Enabled {_ = m.staterace.Release(unsafe.Pointer(m))}// 直接更新第一位即鎖位置為0,直接解鎖new := atomic.AddInt32(&m.state, -mutexLocked)if new != 0 {m.unlockSlow(new)} }

    unlock()方法進(jìn)入后直接更新第一位即鎖位置為0,直接解鎖,new!=0解鎖失敗后進(jìn)入unlockSlow()方法進(jìn)行解鎖操作。

    unlockSlow()

    func (m *Mutex) unlockSlow(new int32) {// ①狀態(tài)不一致,直接拋異常if (new+mutexLocked)&mutexLocked == 0 {throw("sync: unlock of unlocked mutex")}// ②饑餓模式直接喚醒隊列首部的協(xié)程if new&mutexStarving == 0 {old := newfor {// ③如果沒有等待者或協(xié)程,不用喚醒就返回if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {return}// 等待者數(shù)量-1,將喚醒位改成1new = (old - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&m.state, old, new) {// 喚醒一個阻塞的協(xié)程,喚醒的不是第一個等待協(xié)程runtime_Semrelease(&m.sema, false, 1)return}old = m.state}} else {// ④饑餓模式下,將持有鎖交給下一個等待者,此時mutexLocked還為0,但是在饑餓模式下,新協(xié)程不會更新mutexLocked位。runtime_Semrelease(&m.sema, true, 1)} }

    unlockSlow()方法進(jìn)入后首先檢查狀態(tài),如果狀態(tài)不一致,直接拋異常。然后饑餓模式直接喚醒隊列首部的協(xié)程,如果沒有等待協(xié)程,就不喚醒直接返回。等待協(xié)程數(shù)量-1,將喚醒位改成1,喚醒一個阻塞協(xié)程,喚醒的不一定是第一個等待協(xié)程。否則、饑餓模式下,將持有鎖交給下一個等待協(xié)程,此時mutexLocked還為0,但是在饑餓模式下,新的協(xié)程不會更新mutexLocked位。

    總結(jié)

    • 原子性,把一個互斥量鎖定為一個原子操作,保證如果一個協(xié)程鎖定了一個互斥量,這時候其他協(xié)程同一時間不能?成功鎖定這個互斥量。
    • ?唯一性:如果一個協(xié)程鎖定了一個互斥量,在他解鎖之前,其他協(xié)程?無法鎖定這個互斥量。
    • 互斥鎖只能鎖定一次,當(dāng)在解鎖之前再次進(jìn)行加鎖,便會無法加鎖。如果在加鎖前解鎖,便會報錯"panic: sync: unlock of unlocked mutex"。?
    • 互斥鎖無沖突,有沖突時,首先自旋,經(jīng)過短暫自旋后可以獲得鎖,如果自旋無結(jié)果時通過信號通知協(xié)程繼續(xù)等待。

    本文為原創(chuàng)文章,出自guichenglin,轉(zhuǎn)載請粘貼源鏈接,如果未經(jīng)允許轉(zhuǎn)發(fā)后果自負(fù)。

    ?

    總結(jié)

    以上是生活随笔為你收集整理的Go语言 读写锁互斥锁原理剖析(2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。