自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Libtask源碼解析之鎖

開(kāi)發(fā) 前端
libtask中其實(shí)不需要鎖,因?yàn)閘ibtask中協(xié)程是非搶占式的,不存在競(jìng)態(tài)條件。但是libtask還是實(shí)現(xiàn)了一套鎖的機(jī)制。我們看一下這個(gè)鎖機(jī)制的實(shí)現(xiàn)。首先我們看一下結(jié)構(gòu)體。

[[382302]]

本文轉(zhuǎn)載自微信公眾號(hào)「編程雜技」,作者theanarkh 。轉(zhuǎn)載本文請(qǐng)聯(lián)系編程雜技公眾號(hào)。  

 libtask中其實(shí)不需要鎖,因?yàn)閘ibtask中協(xié)程是非搶占式的,不存在競(jìng)態(tài)條件。但是libtask還是實(shí)現(xiàn)了一套鎖的機(jī)制。我們看一下這個(gè)鎖機(jī)制的實(shí)現(xiàn)。首先我們看一下結(jié)構(gòu)體。

  1. struct QLock 
  2.     // 鎖持有者 
  3.     Task    *owner; 
  4.     // 等待該鎖的隊(duì)列 
  5.     Tasklist waiting; 
  6. }; 

接著我們看一下鎖的操作。

加鎖

  1. static int _qlock(QLock *l, int block) 
  2. {     
  3.     // 鎖沒(méi)有持有者,則置當(dāng)前協(xié)程為持有者,直接返回,1表示加鎖成功 
  4.     if(l->owner == nil){ 
  5.         l->owner = taskrunning; 
  6.         return 1; 
  7.     } 
  8.     // 非阻塞,則直接返回,0表示加鎖失敗 
  9.     if(!block) 
  10.         return 0; 
  11.     // 插入等待鎖隊(duì)列 
  12.     addtask(&l->waiting, taskrunning); 
  13.     taskstate("qlock"); 
  14.     // 切換到其他協(xié)程 
  15.     taskswitch(); 
  16.     // 切換回來(lái)時(shí),如果持有鎖的協(xié)程不是當(dāng)前協(xié)程,則異常退出,因?yàn)橹挥谐钟墟i才會(huì)被切換回來(lái),見(jiàn)unqlock 
  17.     if(l->owner != taskrunning){ 
  18.         fprint(2, "qlock: owner=%p self=%p oops\n", l->owner, taskrunning); 
  19.         abort(); 
  20.     } 
  21.     return 1; 

如果當(dāng)前鎖沒(méi)有持有者,則當(dāng)前協(xié)程X就變成鎖的持有者,否則把協(xié)程X插入等待鎖隊(duì)列中,然后讓出cpu,切換到其他協(xié)程。當(dāng)后續(xù)鎖被釋放并被協(xié)程X持有時(shí),協(xié)程X就會(huì)被喚醒繼續(xù)持續(xù)。加鎖可以分為阻塞和非阻塞兩種模式。非阻塞就是加鎖失敗也不會(huì)切換協(xié)程。

  1. // 阻塞式加鎖 
  2. void qlock(QLock *l) 
  3.     _qlock(l, 1); 
  4.  
  5. // 非阻塞式加鎖 
  6. int 
  7. canqlock(QLock *l) 
  8.     return _qlock(l, 0); 

釋放鎖

接下來(lái)我們看一下釋放鎖的邏輯

  1. // 釋放鎖 
  2. void qunlock(QLock *l) 
  3.     Task *ready; 
  4.     // 鎖并沒(méi)有持有者,異常退出 
  5.     if(l->owner == 0){ 
  6.         fprint(2, "qunlock: owner=0\n"); 
  7.         abort(); 
  8.     } 
  9.     // 如果還有協(xié)程在等待該鎖,則置為持有者,并且從等待隊(duì)列中刪除,然后修改狀態(tài)為就緒并加入就緒隊(duì)列 
  10.     if((l->owner = ready = l->waiting.head) != nil){ 
  11.         deltask(&l->waiting, ready); 
  12.         taskready(ready); 
  13.     } 

當(dāng)鎖被釋放時(shí),如果還有協(xié)程在等待該鎖,則從等待隊(duì)列中摘取一個(gè)節(jié)點(diǎn),然后變成鎖的持有者并從等待隊(duì)列中刪除。最后插入就緒隊(duì)列等待調(diào)度。以上是一種互斥鎖的實(shí)現(xiàn)。下面我們?cè)賮?lái)看一下讀寫(xiě)鎖機(jī)制,讀寫(xiě)鎖也是互斥的,但是在某些情況下也可以共享。我們看一下讀寫(xiě)鎖的數(shù)據(jù)結(jié)構(gòu)。

  1. struct RWLock 
  2.     // 正在讀的讀者個(gè)數(shù) 
  3.     int    readers; 
  4.     // 當(dāng)前正在寫(xiě)的寫(xiě)者,只有一個(gè) 
  5.     Task    *writer; 
  6.     // 等待讀和寫(xiě)的隊(duì)列 
  7.     Tasklist rwaiting; 
  8.     Tasklist wwaiting; 
  9. }; 

接著我看一下加鎖邏輯。

加讀鎖

  1. // 加讀鎖 
  2. static int _rlock(RWLock *l, int block) 
  3. {     
  4.     /* 
  5.         沒(méi)有正在寫(xiě)并且沒(méi)有等待寫(xiě),則加鎖成功,并且讀者數(shù)加一 
  6.     */ 
  7.     if(l->writer == nil && l->wwaiting.head == nil){ 
  8.         l->readers++; 
  9.         return 1; 
  10.     } 
  11.     // 非阻塞則直接返回 
  12.     if(!block) 
  13.         return 0; 
  14.     // 插入等待讀隊(duì)列 
  15.     addtask(&l->rwaiting, taskrunning); 
  16.     taskstate("rlock"); 
  17.     // 切換上下文 
  18.     taskswitch(); 
  19.     // 切換回來(lái)了,說(shuō)明加鎖成功 
  20.     return 1; 

當(dāng)且僅當(dāng)沒(méi)有正在寫(xiě)的寫(xiě)者和等待寫(xiě)的寫(xiě)者時(shí),才能加讀鎖成功,否則根據(jù)加鎖模式進(jìn)行下一步處理,直接返回加鎖失敗或者插入等待隊(duì)列,然后切換到其他協(xié)程。我們看到當(dāng)有一個(gè)等待寫(xiě)的協(xié)程時(shí)(l->wwaiting.head != nil),則后續(xù)的讀者就無(wú)法加鎖成功,而是被插入等待隊(duì)列,否則可能會(huì)引起寫(xiě)者饑餓。

加寫(xiě)鎖

  1. // 加寫(xiě)鎖 
  2. static int _wlock(RWLock *l, int block) 
  3. {     
  4.     // 沒(méi)有正在寫(xiě)并且沒(méi)有正在讀,則加鎖成功,并置寫(xiě)者為當(dāng)前協(xié)程 
  5.     if(l->writer == nil && l->readers == 0){ 
  6.         l->writer = taskrunning; 
  7.         return 1; 
  8.     } 
  9.     // 非阻塞則直接返回 
  10.     if(!block) 
  11.         return 0; 
  12.     // 加入等待寫(xiě)隊(duì)列 
  13.     addtask(&l->wwaiting, taskrunning); 
  14.     taskstate("wlock"); 
  15.     // 切換 
  16.     taskswitch(); 
  17.     // 切換回來(lái)說(shuō)明拿到鎖了 
  18.     return 1; 

當(dāng)且僅當(dāng)沒(méi)有正在寫(xiě)的寫(xiě)者和沒(méi)有正在讀的讀者時(shí),才能加寫(xiě)鎖成功。否則類(lèi)似加讀鎖一樣處理。

釋放讀鎖

  1. // 釋放讀鎖 
  2. void runlock(RWLock *l) 
  3.     Task *t; 
  4.     // 讀者減一,如果等于0并且有等待寫(xiě)的協(xié)程,則隊(duì)列第一個(gè)協(xié)程持有該鎖 
  5.     if(--l->readers == 0 && (t = l->wwaiting.head) != nil){ 
  6.         deltask(&l->wwaiting, t); 
  7.         l->writer = t; 
  8.         taskready(t); 
  9.     } 

持有讀鎖,說(shuō)明當(dāng)前肯定沒(méi)有正在寫(xiě)的寫(xiě)者,但是可能有等待寫(xiě)的寫(xiě)者和等待讀的讀者(因?yàn)橛械却龑?xiě)的寫(xiě)者導(dǎo)致無(wú)法加鎖成功)。當(dāng)釋放讀鎖時(shí),如果還有其他讀者,則其他讀者可以繼續(xù)持有鎖,因?yàn)樽x者可以共享讀鎖,而寫(xiě)者保持原來(lái)狀態(tài)。如果這時(shí)候沒(méi)有讀者但是有等待寫(xiě)的寫(xiě)者,則從隊(duì)列中選擇第一個(gè)節(jié)點(diǎn)成為鎖的持有者,其他的寫(xiě)者則繼續(xù)等待,因?yàn)閷?xiě)者不能共享寫(xiě)鎖。

釋放寫(xiě)鎖

  1. // 釋放寫(xiě)鎖 
  2. void wunlock(RWLock *l) 
  3.     Task *t; 
  4.     // 沒(méi)有正在寫(xiě),異常退出 
  5.     if(l->writer == nil){ 
  6.         fprint(2, "wunlock: not locked\n"); 
  7.         abort(); 
  8.     } 
  9.     // 置空,沒(méi)有協(xié)程正在寫(xiě) 
  10.     l->writer = nil; 
  11.     // 有正在讀,異常退出,寫(xiě)的時(shí)候,是無(wú)法讀的 
  12.     if(l->readers != 0){ 
  13.         fprint(2, "wunlock: readers\n"); 
  14.         abort(); 
  15.     } 
  16.     // 釋放寫(xiě)鎖時(shí),優(yōu)先讓讀者持有鎖,因?yàn)樽x者可以共享持有鎖,提高并發(fā) 
  17.     // 讀可以共享,把等待讀的協(xié)程都加入就緒隊(duì)列,并持有鎖 
  18.     while((t = l->rwaiting.head) != nil){ 
  19.         deltask(&l->rwaiting, t); 
  20.         l->readers++; 
  21.         taskready(t); 
  22.     } 
  23.     // 釋放寫(xiě)鎖時(shí),如果又沒(méi)有讀者,并且有等待寫(xiě)的協(xié)程,則隊(duì)列的第一個(gè)等待寫(xiě)的協(xié)程持有鎖 
  24.     if(l->readers == 0 && (t = l->wwaiting.head) != nil){ 
  25.         deltask(&l->wwaiting, t); 
  26.         l->writer = t; 
  27.         taskready(t); 
  28.     } 

持有寫(xiě)鎖,可能有等待寫(xiě)的寫(xiě)者和等待讀的讀者。這里是讀者優(yōu)先持有鎖,因?yàn)樽x者可以共享持有鎖,提高并發(fā),如果沒(méi)有讀者,則再判斷寫(xiě)者。

總結(jié):?jiǎn)渭兊幕コ怄i是比較簡(jiǎn)單的,讀寫(xiě)鎖就相對(duì)復(fù)雜一點(diǎn),主要是要根據(jù)讀鎖和寫(xiě)鎖的特性制定一些策略,比如避免饑餓問(wèn)題。libtask的方式是,加寫(xiě)鎖的時(shí)候,當(dāng)無(wú)法持有鎖的時(shí)候,申請(qǐng)者就會(huì)被插入等待等待隊(duì)列。這個(gè)是沒(méi)有什么好說(shuō)的,加讀者的時(shí)候,情況就復(fù)雜了點(diǎn),如果這時(shí)候有讀者正在持有鎖,理論上,申請(qǐng)者也可以持有鎖,因?yàn)樽x鎖是共享的,但是單純這樣處理的話,可能會(huì)導(dǎo)致等待寫(xiě)的寫(xiě)者一直拿不到鎖,所以這里需要判斷是否有等待寫(xiě)的寫(xiě)者,如果有則當(dāng)前申請(qǐng)者則不能再持有讀鎖,而是要加入等待隊(duì)列。那么在釋放鎖的時(shí)候,當(dāng)釋放讀鎖時(shí),優(yōu)先讓等待寫(xiě)的寫(xiě)者持有鎖,再到等待讀的讀者持有鎖。同樣,當(dāng)釋放寫(xiě)鎖時(shí),優(yōu)先讓讀者持有鎖,這樣就能比較好地平衡讀者和寫(xiě)者持有鎖的機(jī)會(huì)。

 

責(zé)任編輯:武曉燕 來(lái)源: 編程雜技
相關(guān)推薦

2021-02-19 06:56:33

架構(gòu)協(xié)程應(yīng)用

2021-07-02 08:51:09

Redisson分布式鎖公平鎖

2021-06-30 14:56:12

Redisson分布式公平鎖

2022-12-07 08:02:43

Spring流程IOC

2021-07-01 09:42:08

Redisson分布式

2022-02-14 14:47:11

SystemUIOpenHarmon鴻蒙

2016-09-20 10:26:25

LaravelPHPMiddleware

2022-05-17 10:42:36

reboot源碼解析

2016-09-20 10:15:49

LaravelPHPContainer

2022-08-08 08:03:44

MySQL數(shù)據(jù)庫(kù)CBO

2021-07-03 17:45:57

分布式Redisson MultiLock

2015-09-11 09:17:55

JavaJava HashMa

2022-04-28 16:19:30

JS API 接口短信PDU

2020-09-10 09:05:29

UDP服務(wù)器Nodejs

2022-06-13 14:18:39

電源管理子系統(tǒng)耗電量服務(wù)

2021-12-30 22:50:32

KafkaConsumer 源碼

2021-09-16 15:08:08

鴻蒙HarmonyOS應(yīng)用

2011-06-23 15:32:05

Qt Windows消息

2021-11-25 09:54:54

鴻蒙HarmonyOS應(yīng)用

2022-01-06 16:17:58

鴻蒙HarmonyOS應(yīng)用
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)