Go語言 鎖的介紹


鎖的介紹與使用

1 互斥鎖
傳統(tǒng)并發(fā)程序?qū)蚕碣Y源進(jìn)行訪問控制的主要手段,由標(biāo)準(zhǔn)庫代碼包中sync中的Mutex結(jié)構(gòu)體表示帮非。

//Mutex  是互斥鎖譬猫, 零值是解鎖的互斥鎖碌冶, 首次使用后不得復(fù)制互斥鎖。
type Mutex struct {
   state int32
   sema  uint32
}

sync.Mutex類型只有兩個(gè)公開的指針方法

//Locker表示可以鎖定和解鎖的對(duì)象淘邻。
type Locker interface {
   Lock()
   Unlock()
}

//鎖定當(dāng)前的互斥量
//如果鎖已被使用窑眯,則調(diào)用goroutine
//阻塞直到互斥鎖可用屏积。
func (m *Mutex) Lock() 

//對(duì)當(dāng)前互斥量進(jìn)行解鎖
//如果在進(jìn)入解鎖時(shí)未鎖定m,則為運(yùn)行時(shí)錯(cuò)誤磅甩。
//鎖定的互斥鎖與特定的goroutine無關(guān)炊林。
//允許一個(gè)goroutine鎖定Mutex然后安排另一個(gè)goroutine來解鎖它。
func (m *Mutex) Unlock()

聲明一個(gè)互斥鎖:

var mutex sync.Mutex

不像C或Java的鎖類工具更胖,我們可能會(huì)犯一個(gè)錯(cuò)誤:忘記及時(shí)解開已被鎖住的鎖铛铁,從而導(dǎo)致流程異常。但Go由于存在defer却妨,所以此類問題出現(xiàn)的概率極低饵逐。關(guān)于defer解鎖的方式如下:

var mutex sync.Mutex
func Write()  {
   mutex.Lock()
   defer mutex.Unlock()
}

如果對(duì)一個(gè)已經(jīng)上鎖的對(duì)象再次上鎖,那么就會(huì)導(dǎo)致該鎖定操作被阻塞彪标,直到該互斥鎖回到被解鎖狀態(tài)

func main()  {
   var mutex sync.Mutex
   fmt.Println("start lock main")
   mutex.Lock()
   fmt.Println("get locked main")
   for i := 1;i<=3 ;i++  {
      go func(i int) {  
         fmt.Println("start lock ",i)
         mutex.Lock()
         fmt.Println("get locked ",i)
      }(i)
   }

   time.Sleep(time.Second)
   fmt.Println("Unlock the lock main")
   mutex.Unlock()
   fmt.Println("get unlocked main")
   time.Sleep(time.Second)
}

上面的示例中倍权,我們?cè)趂or循環(huán)之前開始加鎖,然后在每一次循環(huán)中創(chuàng)建一個(gè)協(xié)程捞烟,并對(duì)其加鎖薄声,但是由于之前已經(jīng)加鎖了,所以這個(gè)for循環(huán)中的加鎖會(huì)陷入阻塞直到main中的鎖被解鎖题画, time.Sleep(time.Second) 是為了能讓系統(tǒng)有足夠的時(shí)間運(yùn)行for循環(huán)默辨,輸出結(jié)果如下:

start lock main
get locked main
start lock  3
start lock  1
start lock  2
Unlock the lock main
get unlocked main
get locked  3

最終在main解鎖后,三個(gè)協(xié)程會(huì)重新?lián)寠Z互斥鎖權(quán)苍息,最終協(xié)程3獲勝缩幸。

互斥鎖鎖定操作的逆操作并不會(huì)導(dǎo)致協(xié)程阻塞,但是有可能導(dǎo)致引發(fā)一個(gè)無法恢復(fù)的運(yùn)行時(shí)的panic竞思,比如對(duì)一個(gè)未鎖定的互斥鎖進(jìn)行解鎖時(shí)就會(huì)發(fā)生panic表谊。避免這種情況的最有效方式就是使用defer。

我們知道如果遇到panic盖喷,可以使用recover方法進(jìn)行恢復(fù)爆办,但是如果對(duì)重復(fù)解鎖互斥鎖引發(fā)的panic卻是徒勞的(Go 1.8及以后)。

func main()  {
   defer func() {
      fmt.Println("Try to recover the panic")
      if p := recover();p!=nil{
         fmt.Println("recover the panic : ",p)
      }
   }()
   var mutex sync.Mutex
   fmt.Println("start lock")
   mutex.Lock()
   fmt.Println("get locked")
   fmt.Println("unlock lock")
   mutex.Unlock()
   fmt.Println("lock is unlocked")
   fmt.Println("unlock lock again")
   mutex.Unlock()
}

以上代碼試圖對(duì)重復(fù)解鎖引發(fā)的panic進(jìn)行recover课梳,但是我們發(fā)現(xiàn)操作失敗距辆,輸出結(jié)果:

start lock
get locked
fatal error: sync: unlock of unlocked mutex
unlock lock
lock is unlocked
unlock lock again

goroutine 1 [running]:
runtime.throw(0x4c2b46, 0x1e)
    C:/Go/src/runtime/panic.go:619 +0x88 fp=0xc04207dea8 sp=0xc04207de88 pc=0x428978
sync.throw(0x4c2b46, 0x1e)
    C:/Go/src/runtime/panic.go:608 +0x3c fp=0xc04207dec8 sp=0xc04207dea8 pc=0x4288dc
sync.(*Mutex).Unlock(0xc042060080)
    C:/Go/src/sync/mutex.go:184 +0xc9 fp=0xc04207def0 sp=0xc04207dec8 pc=0x456b59
main.main()
    D:/GoDemo/src/MyGo/Demo_04.go:23 +0x1dd fp=0xc04207df88 sp=0xc04207def0 pc=0x48ca9d
runtime.main()
    C:/Go/src/runtime/proc.go:198 +0x20e fp=0xc04207dfe0 sp=0xc04207df88 pc=0x42a21e
runtime.goexit()
    C:/Go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc04207dfe8 sp=0xc04207dfe0 pc=0x44f791

雖然互斥鎖可以被多個(gè)協(xié)程共享余佃,但還是建議將對(duì)同一個(gè)互斥鎖的加鎖解鎖操作放在同一個(gè)層次的代碼中。

2 讀寫鎖
讀寫鎖是針對(duì)讀寫操作的互斥鎖跨算,可以分別針對(duì)讀操作與寫操作進(jìn)行鎖定和解鎖操作 咙冗。
讀寫鎖的訪問控制規(guī)則如下:
①多個(gè)寫操作之間是互斥的
②寫操作與讀操作之間也是互斥的
③多個(gè)讀操作之間不是互斥的
在這樣的控制規(guī)則下,讀寫鎖可以大大降低性能損耗漂彤。

由標(biāo)準(zhǔn)庫代碼包中sync中的RWMutex結(jié)構(gòu)體表示

// RWMutex是一個(gè)讀/寫互斥鎖,可以由任意數(shù)量的讀操作或單個(gè)寫操作持有灾搏。
// RWMutex的零值是未鎖定的互斥鎖挫望。
//首次使用后,不得復(fù)制RWMutex狂窑。
//如果goroutine持有RWMutex進(jìn)行讀取而另一個(gè)goroutine可能會(huì)調(diào)用Lock媳板,那么在釋放初始讀鎖之前,goroutine不應(yīng)該期望能夠獲取讀鎖定泉哈。 
//特別是蛉幸,這種禁止遞歸讀鎖定。 這是為了確保鎖最終變得可用; 阻止的鎖定會(huì)阻止新讀操作獲取鎖定丛晦。
type RWMutex struct {
   w           Mutex  //如果有待處理的寫操作就持有
   writerSem   uint32 // 寫操作等待讀操作完成的信號(hào)量
   readerSem   uint32 //讀操作等待寫操作完成的信號(hào)量
   readerCount int32  // 待處理的讀操作數(shù)量
   readerWait  int32  // number of departing readers
}

sync中的RWMutex有以下幾種方法:

//對(duì)讀操作的鎖定
func (rw *RWMutex) RLock()
//對(duì)讀操作的解鎖
func (rw *RWMutex) RUnlock()
//對(duì)寫操作的鎖定
func (rw *RWMutex) Lock()
//對(duì)寫操作的解鎖
func (rw *RWMutex) Unlock()

//返回一個(gè)實(shí)現(xiàn)了sync.Locker接口類型的值奕纫,實(shí)際上是回調(diào)rw.RLock and rw.RUnlock.
func (rw *RWMutex) RLocker() Locker

Unlock會(huì)試圖喚醒所有因欲進(jìn)行讀鎖定而被阻塞的協(xié)程,而 RUnlock 只會(huì)在已無任何讀鎖定的情況下烫沙,試圖喚醒一個(gè)因欲進(jìn)行寫鎖定而被阻塞的協(xié)程匹层。若對(duì)一個(gè)未被寫鎖定的讀寫鎖進(jìn)行寫解鎖,就會(huì)引發(fā)一個(gè)不可恢復(fù)的panic锌蓄,同理對(duì)一個(gè)未被讀鎖定的讀寫鎖進(jìn)行讀寫鎖也會(huì)如此升筏。

由于讀寫鎖控制下的多個(gè)讀操作之間不是互斥的,因此對(duì)于讀解鎖更容易被忽視瘸爽。對(duì)于同一個(gè)讀寫鎖您访,添加多少個(gè)讀鎖定,就必要有等量的讀解鎖剪决,這樣才能其他協(xié)程有機(jī)會(huì)進(jìn)行操作灵汪。

func main() {
   var rwm sync.RWMutex
   for i := 0; i < 3; i++ {
      go func(i int) {
         fmt.Println("try to lock read ", i)
         rwm.RLock()
         fmt.Println("get locked ", i)
         time.Sleep(time.Second *2)
         fmt.Println("try to unlock for reading ", i)
         rwm.RUnlock()
         fmt.Println("unlocked for reading ", i)
      }(i)
   }
   time.Sleep(time.Millisecond * 1000)
   fmt.Println("try to lock for writing")
   rwm.Lock()
   fmt.Println("locked for writing")
}

上面的示例創(chuàng)建了三個(gè)協(xié)程用于對(duì)讀寫鎖的讀鎖定與讀解鎖操作。在 rwm.Lock()種會(huì)對(duì)main中協(xié)程進(jìn)行寫鎖定昼捍,但是for循環(huán)中的讀解鎖尚未完成识虚,因此會(huì)造成mian中的協(xié)程阻塞。當(dāng)for循環(huán)中的讀解鎖操作都完成后就會(huì)試圖喚醒main中阻塞的協(xié)程妒茬,main中的寫鎖定才會(huì)完成担锤。輸出結(jié)果如下

try to lock read  0
get locked  0
try to lock read  2
get locked  2
try to lock read  1
get locked  1
try to lock for writing
try to unlock for reading  0
unlocked for reading  0
try to unlock for reading  2
unlocked for reading  2
try to unlock for reading  1
unlocked for reading  1
locked for writing
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市乍钻,隨后出現(xiàn)的幾起案子肛循,更是在濱河造成了極大的恐慌铭腕,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件多糠,死亡現(xiàn)場離奇詭異累舷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)夹孔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門被盈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搭伤,你說我怎么就攤上這事只怎。” “怎么了怜俐?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵身堡,是天一觀的道長。 經(jīng)常有香客問我拍鲤,道長贴谎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任季稳,我火速辦了婚禮擅这,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绞幌。我一直安慰自己蕾哟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布莲蜘。 她就那樣靜靜地躺著谭确,像睡著了一般。 火紅的嫁衣襯著肌膚如雪票渠。 梳的紋絲不亂的頭發(fā)上逐哈,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音问顷,去河邊找鬼昂秃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杜窄,可吹牛的內(nèi)容都是我干的肠骆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼塞耕,長吁一口氣:“原來是場噩夢啊……” “哼蚀腿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤莉钙,失蹤者是張志新(化名)和其女友劉穎廓脆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磁玉,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡停忿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚊伞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片席赂。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖时迫,靈堂內(nèi)的尸體忽然破棺而出氧枣,到底是詐尸還是另有隱情,我是刑警寧澤别垮,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站扎谎,受9級(jí)特大地震影響碳想,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毁靶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一胧奔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧预吆,春花似錦龙填、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凤瘦,卻和暖如春宿礁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔬芥。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工梆靖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笔诵。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓返吻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乎婿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子测僵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記,整理的知識(shí)點(diǎn)次酌,也是為了防止忘記恨课,尊重勞動(dòng)成果舆乔,轉(zhuǎn)載注明出處哦!如果你也喜歡剂公,那...
    波波波先森閱讀 11,257評(píng)論 4 56
  • 在上篇中纲辽,我們已經(jīng)討論過如何去實(shí)現(xiàn)一個(gè) Map 了颜武,并且也討論了諸多優(yōu)化點(diǎn)。在下篇中拖吼,我們將繼續(xù)討論如何實(shí)現(xiàn)一個(gè)線...
    一縷殤流化隱半邊冰霜閱讀 7,616評(píng)論 5 41
  • 根據(jù)這么多天的努力與不斷的學(xué)習(xí)鳞上,我們的處理問題的能力得到了很大的提高,但成功的道路還有很長吊档,我們一起加油吧篙议。
    e82dd12e53da閱讀 109評(píng)論 0 0
  • 文|悟恩居士 圖|源自網(wǎng)絡(luò) 多年前的舊文,已發(fā)都市期刊雜志《簡愛》 1. 是在2004年的秋末冬初怠硼。秋日的陽光溫暖...
    悟恩說事閱讀 674評(píng)論 0 6
  • 日總結(jié)20170712 修身:堅(jiān)持跑步鬼贱,看書,做讀書筆記 建功:新店日常物料采購香璃,按時(shí)配發(fā)店鋪訂單 齊家:和姐姐一...
    隔世的風(fēng)閱讀 233評(píng)論 0 0