精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

一個(gè)“蠅量級(jí)”C語(yǔ)言協(xié)程庫(kù)

開發(fā) 后端
協(xié)程(coroutine)顧名思義就是“協(xié)作的例程”(co-operative routines)。跟具有操作系統(tǒng)概念的線程不一樣,協(xié)程是在用戶空間利用程序語(yǔ)言的語(yǔ)法語(yǔ)義就能實(shí)現(xiàn)邏輯上類似多任務(wù)的編程技巧。

協(xié)程(coroutine)顧名思義就是“協(xié)作的例程”(co-operative routines)。跟具有操作系統(tǒng)概念的線程不一樣,協(xié)程是在用戶空間利用程序語(yǔ)言的語(yǔ)法語(yǔ)義就能實(shí)現(xiàn)邏輯上類似多任務(wù)的編程技巧。實(shí)際上協(xié)程的概念比線程還要早,按照 Knuth 的說(shuō)法“子例程是協(xié)程的特例”,一個(gè)子例程就是一次子函數(shù)調(diào)用,那么實(shí)際上協(xié)程就是類 函數(shù)一樣的程序組件,你可以在一個(gè)線程里面輕松創(chuàng)建數(shù)十萬(wàn)個(gè)協(xié)程,就像數(shù)十萬(wàn)次函數(shù)調(diào)用一樣。只不過(guò)子例程只有一個(gè)調(diào)用入口起始點(diǎn),返回之后就結(jié)束了,而 協(xié)程入口既可以是起始點(diǎn),又可以從上一個(gè)返回點(diǎn)繼續(xù)執(zhí)行,也就是說(shuō)協(xié)程之間可以通過(guò) yield 方式轉(zhuǎn)移執(zhí)行權(quán),對(duì)稱(symmetric)、平級(jí)地調(diào)用對(duì)方,而不是像例程那樣上下級(jí)調(diào)用關(guān)系。當(dāng)然 Knuth 的“特例”指的是協(xié)程也可以模擬例程那樣實(shí)現(xiàn)上下級(jí)調(diào)用關(guān)系,這就叫非對(duì)稱協(xié)程(asymmetric coroutines)。

基于事件驅(qū)動(dòng)模型

我們舉一個(gè)例子來(lái)看看一種對(duì)稱協(xié)程調(diào)用場(chǎng)景,大家最熟悉的“生產(chǎn)者-消費(fèi)者”事件驅(qū)動(dòng)模型,一個(gè)協(xié)程負(fù)責(zé)生產(chǎn)產(chǎn)品并將它們加入隊(duì)列,另一個(gè)負(fù)責(zé)從隊(duì)列中取出產(chǎn)品并使用它。為了提高效率,你想一次增加或刪除多個(gè)產(chǎn)品。偽代碼可以是這樣的:

  1. # producer coroutine 
  2. loop 
  3. while queue is not full 
  4.   create some new items 
  5.   add the items to queue 
  6. yield to consumer 
  7.   
  8. # consumer coroutine 
  9. loop 
  10. while queue is not empty 
  11.   remove some items from queue 
  12.   use the items 
  13. yield to producer 

大多數(shù)教材上拿這種模型作為多線程的例子,實(shí)際上多線程在此的應(yīng)用還是顯得有點(diǎn)“重量級(jí)”,由于缺乏 yield 語(yǔ)義,線程之間不得不使用同步機(jī)制來(lái)避免產(chǎn)生全局資源的竟態(tài),這就不可避免產(chǎn)生了休眠、調(diào)度、切換上下文一類的系統(tǒng)開銷,而且線程調(diào)度還會(huì)產(chǎn)生時(shí)序上的不 確定性。而對(duì)于協(xié)程來(lái)說(shuō),“掛起”的概念只不過(guò)是轉(zhuǎn)讓代碼執(zhí)行權(quán)并調(diào)用另外的協(xié)程,待到轉(zhuǎn)讓的協(xié)程告一段落后重新得到調(diào)用并從掛起點(diǎn)“喚醒”,這種協(xié)程間 的調(diào)用是邏輯上可控的,時(shí)序上確定的,可謂一切盡在掌握中。

當(dāng)今一些具備協(xié)程語(yǔ)義的語(yǔ)言,比較重量級(jí)的如C#、erlang、golang,以及輕量級(jí)的python、lua、javascript、 ruby,還有函數(shù)式的scala、scheme等。相比之下,作為原生態(tài)語(yǔ)言的 C 反而處于尷尬的地位,原因在于 C 依賴于一種叫做棧幀的 例程調(diào)用,例程內(nèi)部的狀態(tài)量和返回值都保留在堆棧上,這意味著生產(chǎn)者和消費(fèi)者相互之間無(wú)法實(shí)現(xiàn)平級(jí)調(diào)用,當(dāng)然你可以改寫成把生產(chǎn)者作為主例程然后將產(chǎn)品作 為傳遞參數(shù)調(diào)用消費(fèi)者例程,這樣的代碼寫起來(lái)費(fèi)力不討好而且看起來(lái)會(huì)很難受,特別當(dāng)協(xié)程數(shù)目達(dá)到十萬(wàn)數(shù)量級(jí),這種寫法就過(guò)于僵化了。

這就引出了協(xié)程的概念,如果將每個(gè)協(xié)程的上下文(比如程序計(jì)數(shù)器)保存在其它地方而不是堆棧上,協(xié)程之間相互調(diào)用時(shí),被調(diào)用的協(xié)程只要從堆棧以外的地方恢復(fù)上次出讓點(diǎn)之前的上下文即可,這有點(diǎn)類似于 CPU 的上下文切換,遺憾的是似乎只有更底層的匯編語(yǔ)言才能做到這一點(diǎn)。

難道 C 語(yǔ)言只能用多線程嗎?幸運(yùn)的是,C 標(biāo)準(zhǔn)庫(kù)給我們提供了兩種協(xié)程調(diào)度原語(yǔ):一種是setjmp/longjmp,另一種是ucontext 組件,它們內(nèi)部(當(dāng)然是用匯編語(yǔ)言)實(shí)現(xiàn)了協(xié)程的上下文切換,相較之下前者在應(yīng)用上會(huì)產(chǎn)生相當(dāng)?shù)牟淮_定性(比如不好封裝,具體說(shuō)明參考聯(lián)機(jī)文檔),所以后者應(yīng)用更廣泛一些,網(wǎng)上絕大多數(shù) C 協(xié)程庫(kù)也是基于 ucontext 組件實(shí)現(xiàn)的。

“蠅量級(jí)”的協(xié)程庫(kù)

在此,我來(lái)介紹一種“蠅量級(jí)”的開源 C 協(xié)程庫(kù) protothreads。 這是一個(gè)全部用 ANSI C 寫成的庫(kù),之所以稱為“蠅量級(jí)”的,就是說(shuō),實(shí)現(xiàn)已經(jīng)不能再精簡(jiǎn)了,幾乎就是原語(yǔ)級(jí)別。事實(shí)上 protothreads 整個(gè)庫(kù)不需要鏈接加載,因?yàn)樗性创a都是頭文件,類似于 STL 這樣不依賴任何第三方庫(kù),在任何平臺(tái)上可移植;總共也就 5 個(gè)頭文件,有效代碼量不足 100 行;API 都是宏定義的,所以不存在調(diào)用開銷;最后,每個(gè)協(xié)程的空間開銷是 2 個(gè)字節(jié)(是的,你沒有看錯(cuò),就是一個(gè) short 單位的“棧”!)當(dāng)然這種精簡(jiǎn)是要以使用上的局限為代價(jià)的,接下來(lái)的分析會(huì)說(shuō)明這一點(diǎn)。

先來(lái)看看 protothreads 作者,Adam Dunkels,一位來(lái)自瑞典皇家理工學(xué)院的計(jì)算機(jī)天才帥哥。話說(shuō)這哥們挺有意思的,寫了好多輕量級(jí)的作品,都是 BSD 許可證。順便說(shuō)一句,輕量級(jí)開源軟件全世界多如牛毛,可像這位哥們寫得如此出名的并不多。比如嵌入式網(wǎng)絡(luò)操作系統(tǒng) Contiki,國(guó)人耳熟能詳?shù)?TCP/IP 協(xié)議棧 uIP 和 lwIP 也是出自其手。上述這些軟件都是經(jīng)過(guò)數(shù)十年企業(yè)級(jí)應(yīng)用的考驗(yàn),質(zhì)量之高可想而知。

很多人會(huì)好奇如此“蠅量級(jí)”的代碼究竟是怎么實(shí)現(xiàn)的呢?在分析 protothreads 源碼之前,我先來(lái)給大家補(bǔ)一補(bǔ) C 語(yǔ)言的基礎(chǔ)課;-^)簡(jiǎn)而言之,這利用了 C 語(yǔ)言特性上的一個(gè)“奇技淫巧”,而且這種技巧恐怕連許多具備十年以上經(jīng)驗(yàn)的 C 程序員老手都不見得知曉。當(dāng)然這里先要聲明我不是推薦大家都這么用,實(shí)際上這是以破壞語(yǔ)言的代碼規(guī)范為代價(jià),在一些嚴(yán)肅的項(xiàng)目工程中需要謹(jǐn)慎對(duì)待,除非你 想被炒魷魚。

C 語(yǔ)言的“yield 語(yǔ)義”

下面的教程來(lái)自于一位 ARM 工程師、天才黑客 Simon Tatham(開源 Telnet/SSH 客戶端 PuTTY 和匯編器 NASM 的作者,吐槽一句,PuTTY的源碼號(hào)稱是所有正式項(xiàng)目里最難 hack 的 C,你應(yīng)該猜到作者是什么語(yǔ)言出身)的博文:Coroutines in C。中文譯文在這里。

我們知道 python 的 yield 語(yǔ)義功能類似于一種迭代生成器,函數(shù)會(huì)保留上次的調(diào)用狀態(tài),并在下次調(diào)用時(shí)會(huì)從上個(gè)返回點(diǎn)繼續(xù)執(zhí)行。用 C 語(yǔ)言來(lái)寫就像這樣:

  1. int function(void) { 
  2.   int i; 
  3.   for (i = 0; i < 10; i++) 
  4.     return i;   /* won't work, but wouldn't it be nice */ 

連續(xù)對(duì)它調(diào)用 10 次,它能分別返回 0 到 9。該怎樣實(shí)現(xiàn)呢?可以利用 goto 語(yǔ)句,如果我們?cè)诤瘮?shù)中加入一個(gè)狀態(tài)變量,就可以這樣實(shí)現(xiàn):

  1. int function(void) { 
  2.   static int i, state = 0; 
  3.   switch (state) { 
  4.     case 0: goto LABEL0; 
  5.     case 1: goto LABEL1; 
  6.   } 
  7.   LABEL0: /* start of function */ 
  8.   for (i = 0; i < 10; i++) { 
  9.     state = 1; /* so we will come back to LABEL1 */ 
  10.     return i; 
  11.     LABEL1:; /* resume control straight after the return */ 
  12.   } 

#p#

這個(gè)方法是可行的。我們?cè)谒行枰?yield 的位置都加上標(biāo)簽:起始位置加一個(gè),還有所有 return 語(yǔ)句之后都加一個(gè)。每個(gè)標(biāo)簽用數(shù)字編號(hào),我們?cè)跔顟B(tài)變量中保存這個(gè)編號(hào),這樣就能在我們下次調(diào)用時(shí)告訴我們應(yīng)該跳到哪個(gè)標(biāo)簽上。每次返回前,更新狀態(tài)變 量,指向到正確的標(biāo)簽;不論調(diào)用多少次,針對(duì)狀態(tài)變量的 switch 語(yǔ)句都能找到我們要跳轉(zhuǎn)到的位置。

但這還是難看得很。最糟糕的部分是所有的標(biāo)簽都需要手工維護(hù),還必須保證函數(shù)中的標(biāo)簽和開頭 switch 語(yǔ)句中的一致。每次新增一個(gè) return 語(yǔ)句,就必須想一個(gè)新的標(biāo)簽名并將其加到 switch 語(yǔ)句中;每次刪除 return 語(yǔ)句時(shí),同樣也必須刪除對(duì)應(yīng)的標(biāo)簽。這使得維護(hù)代碼的工作量增加了一倍。

仔細(xì)想想,其實(shí)我們可以不用 switch 語(yǔ)句來(lái)決定要跳轉(zhuǎn)到哪里去執(zhí)行,而是直接利用 switch 語(yǔ)句本身來(lái)實(shí)現(xiàn)跳轉(zhuǎn)

  1. int function(void) { 
  2.   static int i, state = 0; 
  3.   switch (state) { 
  4.     case 0: /* start of function */ 
  5.     for (i = 0; i < 10; i++) { 
  6.       state = 1; /* so we will come back to "case 1" */ 
  7.       return i; 
  8.       case 1:; /* resume control straight after the return */ 
  9.     } 
  10.   } 

酷!沒想到 switch-case 語(yǔ)句可以這樣用,其實(shí)說(shuō)白了 C 語(yǔ)言就是脫胎于匯編語(yǔ)言的,switch-case 跟 if-else 一樣,無(wú)非就是匯編的條件跳轉(zhuǎn)指令的另類實(shí)現(xiàn)而已(這也間接解釋了為何匯編程序員經(jīng)常揶揄 C 語(yǔ)言是“大便一樣的代碼”)。我們還可以用 __LINE__ 宏使其更加一般化:

  1. int function(void) { 
  2.   static int i, state = 0; 
  3.   switch (state) { 
  4.     case 0: /* start of function */ 
  5.     for (i = 0; i < 10; i++) { 
  6.       state = __LINE__ + 2; /* so we will come back to "case __LINE__" */ 
  7.       return i; 
  8.       case __LINE__:; /* resume control straight after the return */ 
  9.     } 
  10.   } 

這樣一來(lái)我們可以用宏提煉出一種范式,封裝成組件:

  1. #define Begin() static int state=0; switch(state) { case 0: 
  2. #define Yield(x) do { state=__LINE__; return x; case __LINE__:; } while (0) 
  3. #define End() } 
  4. int function(void) { 
  5.   static int i; 
  6.   Begin(); 
  7.   for (i = 0; i < 10; i++) 
  8.     Yield(i); 
  9.   End(); 

怎么樣,看起來(lái)像不像發(fā)明了一種全新的語(yǔ)言?實(shí)際上我們利用了 switch-case 的分支跳轉(zhuǎn)特性,以及預(yù)編譯的 __LINE__ 宏,實(shí)現(xiàn)了一種隱式狀態(tài)機(jī),最終實(shí)現(xiàn)了“yield 語(yǔ)義”。

還有一個(gè)問(wèn)題,當(dāng)你歡天喜地地將這種鮮為人知的技巧運(yùn)用到你的項(xiàng)目中,并成功地拿去向你的上司邀功問(wèn)賞的時(shí)候,你的上司會(huì)怎樣看待你的代碼呢?你的 宏定義中大括號(hào)沒有匹配完整,在代碼塊中包含了未用到的 case,Begin 和 Yield 宏里面不完整的七拼八湊……你簡(jiǎn)直就是公司里不遵守編碼規(guī)范的反面榜樣!

別著急,在原文中 Simon Tatham 大牛幫你找到一個(gè)堅(jiān)定的反駁理由,我覺得對(duì)程序員來(lái)說(shuō)簡(jiǎn)直是金玉良言。

將編程規(guī)范用在這里是不對(duì)的。文章里給出的示例代碼不是很長(zhǎng),也不很復(fù)雜,即便以狀態(tài)機(jī)的方式改寫還是能夠看懂的。但是隨著代碼越來(lái)越長(zhǎng),改寫的難度將越來(lái)越大,改寫對(duì)直觀性造成的損失也變得相當(dāng)相當(dāng)大。

想一想,一個(gè)函數(shù)如果包含這樣的小代碼塊:

  1. case STATE1: 
  2. /* perform some activity */ 
  3. if (condition) state = STATE2; else state = STATE3; 

對(duì)于看代碼的人說(shuō),這和包含下面小代碼塊的函數(shù)沒有多大區(qū)別:

  1. LABEL1: 
  2. /* perform some activity */ 
  3. if (condition) goto LABEL2; else goto LABEL3; 

是的,這兩個(gè)函數(shù)的結(jié)構(gòu)在視覺上是一樣的,而對(duì)于函數(shù)中實(shí)現(xiàn)的算法,兩個(gè)函數(shù)都一樣不利于查看。因?yàn)槟闶褂脜f(xié)程的宏而炒你魷魚的人,一樣會(huì)因?yàn)槟銓?的函數(shù)是由小塊的代碼和 goto 語(yǔ)句組成而吼著炒了你。只是這次他們沒有冤枉你,因?yàn)橄衲菢釉O(shè)計(jì)的函數(shù)會(huì)嚴(yán)重?cái)_亂算法的結(jié)構(gòu)。

編程規(guī)范的目標(biāo)就是為了代碼清晰。如果將一些重要的東西,像 switch、return 以及 case 語(yǔ)句,隱藏到起“障眼”作用的宏中,從編程規(guī)范的角度講,可以說(shuō)你擾亂了程序的語(yǔ)法結(jié)構(gòu),并且違背了代碼清晰這一要求。但是我們這樣做是為了突出程序的算 法結(jié)構(gòu),而算法結(jié)構(gòu)恰恰是看代碼的人更想了解的。

任何編程規(guī)范,堅(jiān)持犧牲算法清晰度來(lái)?yè)Q取語(yǔ)法清晰度的,都應(yīng)該重寫。如果你的上司因?yàn)槭褂昧诉@一技巧而解雇你,那么在保安把你往外拖的時(shí)候要不斷告訴他這一點(diǎn)。

原文作者最后給出了一個(gè) MIT 許可證的 coroutine.h 頭文件。值得一提的是,正如文中所說(shuō),這種協(xié)程實(shí)現(xiàn)方法有個(gè)使用上的局限,就是協(xié)程調(diào)度狀態(tài)的保存依賴于 static 變量,而不是堆棧上的局部變量, 實(shí)際上也無(wú)法用局部變量(堆棧)來(lái)保存狀態(tài),這就使得代碼不具備可重入性和多線程應(yīng)用。后來(lái)作者補(bǔ)充了一種技巧,就是將局部變量包裝成函數(shù)參數(shù)傳入的一個(gè) 虛構(gòu)的上下文結(jié)構(gòu)體指針,然后用動(dòng)態(tài)分配的堆來(lái)“模擬”堆棧,解決了線程可重入問(wèn)題。但這樣一來(lái)反而有損代碼清晰,比如所有局部變量都要寫成對(duì)象成員的引 用方式,特別是局部變量很多的時(shí)候很麻煩,再比如宏定義 malloc/free 的玩法過(guò)于托大,不易控制,搞不好還增加了被炒魷魚的風(fēng)險(xiǎn)(只不過(guò)這次是你活該)。

我個(gè)人認(rèn)為,既然協(xié)程本身是一種單線程的方案,那么我們應(yīng)該假定應(yīng)用環(huán)境是單線程的,不存在代碼重入問(wèn)題,所以我們可以大膽地使用 static 變量,維持代碼的簡(jiǎn)潔和可讀性。事實(shí)上我們也不應(yīng)該在多線程環(huán)境下考慮使用這么簡(jiǎn)陋的協(xié)程,非要用的話,前面提到 glibc 的 ucontext 組件也是一種可行的替代方案,它提供了一種協(xié)程私有堆棧的上下文,當(dāng)然這種用法在跨線程上也并非沒有限制,請(qǐng)仔細(xì)閱讀聯(lián)機(jī)文檔。

#p#

Protothreads的上下文

感謝 Simon Tatham 的淳淳教誨,接下來(lái)我們可以 hack 一下源碼了。先來(lái)看看實(shí)現(xiàn) protothreads 的數(shù)據(jù)結(jié)構(gòu), 實(shí)際上它就是協(xié)程的上下文結(jié)構(gòu)體,用以保存狀態(tài)變量,相信你很快就明白為何它的“堆棧”只有 2 個(gè)字節(jié):

  1. struct pt { 
  2.   lc_t lc; 

里面只有一個(gè) short 類型的變量,實(shí)際上它是用來(lái)保存上一次出讓點(diǎn)的程序計(jì)數(shù)器。這也映證了協(xié)程比線程的靈活之處,就是協(xié)程可以是 stackless 的,如果需要實(shí)現(xiàn)的功能很單一,比如像生產(chǎn)者-消費(fèi)者模型那樣用來(lái)做事件通知,那么實(shí)際上協(xié)程需要保存的狀態(tài)變量?jī)H僅是一個(gè)程序計(jì)數(shù)器即可。像 python generator 也是 stackless 的,當(dāng)然實(shí)現(xiàn)一個(gè)迭代生成器可能還需要保留上一個(gè)迭代值,前面 C 的例子是用 static 變量保存,你也可以設(shè)置成員變量添加到上下文結(jié)構(gòu)體里面。如果你真的不確定用協(xié)程調(diào)度時(shí)需要保存多少狀態(tài)變量,那還是用 ucontext 好了,它的上下文提供了堆棧和信號(hào),但是由用戶負(fù)責(zé)分配資源,詳細(xì)使用方法見聯(lián)機(jī)文檔。

  1. typedef struct ucontext { 
  2.   struct ucontext_t *uc_link; 
  3.   sigset_t uc_sigmask; 
  4.   stack_t uc_stack; 
  5.   ... 
  6. } ucontext_t; 

Protothreads的原語(yǔ)和組件

有點(diǎn)扯遠(yuǎn)了,回到 protothreads,看看提供的協(xié)程“原語(yǔ)”。有兩種實(shí)現(xiàn)方法,在 ANSI C 下,就是傳統(tǒng)的 switch-case 語(yǔ)句:

  1. #define LC_INIT(s) s = 0;  // 源碼中是有分號(hào)的,一個(gè)低級(jí) bug,啊哈~ 
  2. #define LC_RESUME(s) switch (s) { case 0: 
  3. #define LC_SET(s) s = __LINE__; case __LINE__: 
  4. #define LC_END(s) } 

但這種“原語(yǔ)”有個(gè)難以察覺的缺陷:就是你無(wú)法在 LC_RESUME 和 LC_END (或者包含它們的組件)之間的代碼中使用 switch-case語(yǔ)句,因?yàn)檫@會(huì)引起外圍的 switch 跳轉(zhuǎn)錯(cuò)誤!為 此,protothreads 又實(shí)現(xiàn)了基于 GNU C 的調(diào)度“原語(yǔ)”。在 GNU C 下還有一種語(yǔ)法糖叫做標(biāo)簽指針,就是在一個(gè) label 前面加 &&(不是地址的地址,是 GNU 自定義的符號(hào)),可以用 void 指針類型保存,然后 goto 跳轉(zhuǎn):

  1. typedef void * lc_t; 
  2. #define LC_INIT(s) s = NULL 
  3. #define LC_RESUME(s) \ 
  4.   do { \ 
  5.     if (s != NULL) { \ 
  6.       goto *s; \ 
  7.     } 
  8.   } while (0) 
  9. #define LC_CONCAT2(s1, s2) s1##s2 
  10. #define LC_CONCAT(s1, s2) LC_CONCAT2(s1, s2) 
  11. #define LC_SET(s) \ 
  12.   do { \ 
  13.     LC_CONCAT(LC_LABEL, __LINE__): \ 
  14.     (s) = &&LC_CONCAT(LC_LABEL, __LINE__); \ 
  15.   } while (0) 

好了,有了前面的基礎(chǔ)知識(shí),理解這些“原語(yǔ)”就是小菜一疊,下面看看如何建立“組件”,同時(shí)也是 protothreads API,我們先定義四個(gè)退出碼作為協(xié)程的調(diào)度狀態(tài)機(jī)

  1. #define PT_WAITING 0 
  2. #define PT_YIELDED 1 
  3. #define PT_EXITED  2 
  4. #define PT_ENDED   3 

下面這些 API 可直接在應(yīng)用程序中調(diào)用:

  1. /* 初始化一個(gè)協(xié)程,也即初始化狀態(tài)變量 */ 
  2. #define PT_INIT(pt) LC_INIT((pt)->lc) 
  3.   
  4. /* 聲明一個(gè)函數(shù),返回值為 char 即退出碼,表示函數(shù)體內(nèi)使用了 proto thread,(個(gè)人覺得有些多此一舉) */ 
  5. #define PT_THREAD(name_args) char name_args 
  6.   
  7. /* 協(xié)程入口點(diǎn), PT_YIELD_FLAG=0表示出讓,=1表示不出讓,放在 switch 語(yǔ)句前面,下次調(diào)用的時(shí)候可以跳轉(zhuǎn)到上次出讓點(diǎn)繼續(xù)執(zhí)行 */ 
  8. #define PT_BEGIN(pt) { char PT_YIELD_FLAG = 1; LC_RESUME((pt)->lc) 
  9.   
  10. /* 協(xié)程退出點(diǎn),至此一個(gè)協(xié)程算是終止了,清空所有上下文和標(biāo)志 */ 
  11. #define PT_END(pt) LC_END((pt)->lc); PT_YIELD_FLAG = 0; \ 
  12.                    PT_INIT(pt); return PT_ENDED; } 
  13.   
  14. /* 協(xié)程出讓點(diǎn),如果此時(shí)協(xié)程狀態(tài)變量 lc 已經(jīng)變?yōu)?nbsp;__LINE__ 跳轉(zhuǎn)過(guò)來(lái)的,那么 PT_YIELD_FLAG = 1,表示從出讓點(diǎn)繼續(xù)執(zhí)行。 */ 
  15. #define PT_YIELD(pt)        \ 
  16.   do {            \ 
  17.     PT_YIELD_FLAG = 0;        \ 
  18.     LC_SET((pt)->lc);       \ 
  19.     if(PT_YIELD_FLAG == 0) {      \ 
  20.       return PT_YIELDED;      \ 
  21.     }           \ 
  22.   } while(0) 
  23.   
  24. /* 附加出讓條件 */ 
  25. #define PT_YIELD_UNTIL(pt, cond)    \ 
  26.   do {            \ 
  27.     PT_YIELD_FLAG = 0;        \ 
  28.     LC_SET((pt)->lc);       \ 
  29.     if((PT_YIELD_FLAG == 0) || !(cond)) { \ 
  30.       return PT_YIELDED;      \ 
  31.     }           \ 
  32.   } while(0) 
  33.   
  34. /* 協(xié)程阻塞點(diǎn)(blocking),本質(zhì)上等同于 PT_YIELD_UNTIL,只不過(guò)退出碼是 PT_WAITING,用來(lái)模擬信號(hào)量同步 */ 
  35. #define PT_WAIT_UNTIL(pt, condition)          \ 
  36.   do {            \ 
  37.     LC_SET((pt)->lc);       \ 
  38.     if(!(condition)) {        \ 
  39.       return PT_WAITING;      \ 
  40.     }           \ 
  41.   } while(0) 
  42.   
  43. /* 同 PT_WAIT_UNTIL 條件反轉(zhuǎn) */ 
  44. #define PT_WAIT_WHILE(pt, cond)  PT_WAIT_UNTIL((pt), !(cond)) 
  45.   
  46. /* 協(xié)程調(diào)度,調(diào)用協(xié)程 f 并檢查它的退出碼,直到協(xié)程終止返回 0,否則返回 1。 */ 
  47. #define PT_SCHEDULE(f) ((f) < PT_EXITED) 
  48.   
  49. /* 這用于非對(duì)稱協(xié)程,調(diào)用者是主協(xié)程,pt 是和子協(xié)程 thread (可以是多個(gè))關(guān)聯(lián)的上下文句柄,主協(xié)程阻塞自己調(diào)度子協(xié)程,直到所有子協(xié)程終止 */ 
  50. #define PT_WAIT_THREAD(pt, thread) PT_WAIT_WHILE((pt), PT_SCHEDULE(thread)) 
  51.   
  52. /* 用于協(xié)程嵌套調(diào)度,child 是子協(xié)程的上下文句柄 */ 
  53. #define PT_SPAWN(pt, child, thread)   \ 
  54.   do {            \ 
  55.     PT_INIT((child));       \ 
  56.     PT_WAIT_THREAD((pt), (thread));   \ 
  57.   } while(0) 

暫時(shí)介紹這么多,用戶還可以根據(jù)自己的需求隨意擴(kuò)展組件,比如實(shí)現(xiàn)信號(hào)量,你會(huì)發(fā)現(xiàn)脫離了操作系統(tǒng)環(huán)境下的信號(hào)量竟是如此簡(jiǎn)單:

  1. struct pt_sem { 
  2.   unsigned int count; 
  3. }; 
  4.   
  5. #define PT_SEM_INIT(s, c) (s)->count = c 
  6.   
  7. #define PT_SEM_WAIT(pt, s)  \ 
  8.   do {            \ 
  9.     PT_WAIT_UNTIL(pt, (s)->count > 0);    \ 
  10.     --(s)->count;       \ 
  11.   } while(0) 
  12.   
  13. #define PT_SEM_SIGNAL(pt, s) ++(s)->count 

這些應(yīng)該不需要我多說(shuō)了吧,呵呵,讓我們回到最初例舉的生產(chǎn)者-消費(fèi)者模型,看看protothreads表現(xiàn)怎樣。

#p#

Protothreads實(shí)戰(zhàn)

  1. #include "pt-sem.h" 
  2.   
  3. #define NUM_ITEMS 32 
  4. #define BUFSIZE 8 
  5.   
  6. static struct pt_sem mutex, full, empty; 
  7.   
  8. PT_THREAD(producer(struct pt *pt)) 
  9.   static int produced; 
  10.   
  11.   PT_BEGIN(pt); 
  12.   for (produced = 0; produced < NUM_ITEMS; ++produced) { 
  13.     PT_SEM_WAIT(pt, &full); 
  14.     PT_SEM_WAIT(pt, &mutex); 
  15.     add_to_buffer(produce_item()); 
  16.     PT_SEM_SIGNAL(pt, &mutex); 
  17.     PT_SEM_SIGNAL(pt, &empty); 
  18.   } 
  19.   PT_END(pt); 
  20.   
  21. PT_THREAD(consumer(struct pt *pt)) 
  22.   static int consumed; 
  23.   
  24.   PT_BEGIN(pt); 
  25.   for (consumed = 0; consumed < NUM_ITEMS; ++consumed) { 
  26.     PT_SEM_WAIT(pt, &empty); 
  27.     PT_SEM_WAIT(pt, &mutex); 
  28.     consume_item(get_from_buffer()); 
  29.     PT_SEM_SIGNAL(pt, &mutex); 
  30.     PT_SEM_SIGNAL(pt, &full); 
  31.   } 
  32.   PT_END(pt); 
  33.   
  34. PT_THREAD(driver_thread(struct pt *pt)) 
  35.   static struct pt pt_producer, pt_consumer; 
  36.   
  37.   PT_BEGIN(pt); 
  38.   PT_SEM_INIT(&empty, 0); 
  39.   PT_SEM_INIT(&full, BUFSIZE); 
  40.   PT_SEM_INIT(&mutex, 1); 
  41.   PT_INIT(&pt_producer); 
  42.   PT_INIT(&pt_consumer); 
  43.   PT_WAIT_THREAD(pt, producer(&pt_producer) & consumer(&pt_consumer)); 
  44.   PT_END(pt); 

源碼包中的 example-buffer.c 包含了可運(yùn)行的完整示例,我就不全部貼了。整體框架就是一個(gè) asymmetric coroutines,包括一個(gè)主協(xié)程 driver_thread 和兩個(gè)子協(xié)程 producer 和 consumer ,其實(shí)不用多說(shuō)大家也懂的,代碼非常清晰直觀。我們完全可以通過(guò)單線程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的事件處理需求,你可以任意添加數(shù)十萬(wàn)個(gè)協(xié)程,幾乎不會(huì)引起任何額外的 系統(tǒng)開銷和資源占用。唯一需要留意的地方就是沒有一個(gè)局部變量,因?yàn)?protothreads 是 stackless 的,但這不是問(wèn)題,首先我們已經(jīng)假定運(yùn)行環(huán)境是單線程的,其次在一個(gè)簡(jiǎn)化的需求下也用不了多少“局部變量”。如果在協(xié)程出讓時(shí)需要保存一些額外的狀態(tài)量, 像迭代生成器,只要數(shù)目和大小都是確定并且可控的話,自行擴(kuò)展協(xié)程上下文結(jié)構(gòu)體即可。

當(dāng)然這不是說(shuō) protothreads 是萬(wàn)能的,它只是貢獻(xiàn)了一種模型,你要使用它首先就得學(xué)會(huì)適應(yīng)它。下面列舉一些 protothreads 的使用限制:

  • 由于協(xié)程是stackless的,盡量不要使用局部變量,除非該變量對(duì)于協(xié)程狀態(tài)是無(wú)關(guān)緊要的,同理可推,協(xié)程所在的代碼是不可重入的。
  • 如果協(xié)程使用 switch-case 原語(yǔ)封裝的組件,那么禁止在實(shí)際應(yīng)用中使用 switch-case 語(yǔ)句,除非用 GNU C 語(yǔ)法中的標(biāo)簽指針替代。
  • 一個(gè)協(xié)程內(nèi)部可以調(diào)用其它例程,比如庫(kù)函數(shù)或系統(tǒng)調(diào)用,但必須保證該例程是非阻塞的,否則所在線程內(nèi)的所有協(xié)程都將被阻塞。畢竟線程才是執(zhí)行的最小單位,協(xié)程不過(guò)是按“時(shí)間片輪度”的例程而已。

官網(wǎng)上還例舉了更多實(shí)例,都非常實(shí)用。另外,一個(gè)叫 Craig Graham 的工程師擴(kuò)展了 pt.h,使得 protothreads 支持 sleep/wake/kill 等操作,文件在此 graham-pt.h。

協(xié)程庫(kù) DIY 攻略

看到這里,手養(yǎng)的你是否想迫不及待地 DIY 一個(gè)協(xié)程組件呢?哪怕很多動(dòng)態(tài)語(yǔ)言本身已經(jīng)支持了協(xié)程語(yǔ)義,很多 C 程序員仍然傾向于自己實(shí)現(xiàn)組件,網(wǎng)上很多開源代碼底層用的主要還是 glibc 的 ucontext 組件,畢竟提供堆棧的協(xié)程組件使用起來(lái)更加通用方便。你可以自己寫一個(gè)調(diào)度器,然后模擬線程上下文,再然后……你就能搞出一個(gè)跨平臺(tái)的COS了(笑)。 GNU Pth 線程庫(kù)就是這么實(shí)現(xiàn)的,其原作者德國(guó)人 Ralf S. Engelschall (又是個(gè)開源大牛,還寫了 OpenSSL 等許多作品)就寫了一篇論文教大家如何實(shí)現(xiàn)一個(gè)線程庫(kù)。另外 protothreads 官網(wǎng)上也有一大堆推薦閱讀。Have fun!

原文鏈接:http://coolshell.cn/articles/10975.html

責(zé)任編輯:陳四芳 來(lái)源: 酷殼網(wǎng)
相關(guān)推薦

2025-06-26 04:10:00

2022-09-06 20:30:48

協(xié)程Context主線程

2023-11-04 20:00:02

C++20協(xié)程

2021-09-16 09:59:13

PythonJavaScript代碼

2022-09-12 06:35:00

C++協(xié)程協(xié)程狀態(tài)

2021-10-27 11:29:32

框架Web開發(fā)

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2023-12-07 12:59:46

C語(yǔ)言循環(huán)隊(duì)列代碼

2024-09-25 08:28:45

2022-09-10 18:51:09

C++協(xié)程主線程

2021-02-19 06:56:33

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

2022-09-30 13:57:15

JSON解析C語(yǔ)言

2023-10-24 19:37:34

協(xié)程Java

2025-02-08 09:13:40

2021-12-09 06:41:56

Python協(xié)程多并發(fā)

2019-12-13 19:00:26

PekwmLinux桌面

2009-07-31 17:14:19

C#語(yǔ)言Web程序

2020-08-04 10:56:09

進(jìn)程線程協(xié)程

2020-06-10 08:23:44

JavaScript開發(fā)Web

2022-08-31 12:48:48

TinyDBPython數(shù)據(jù)庫(kù)
點(diǎn)贊
收藏

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

超碰97人人做人人爱少妇| 欧美三级日本三级少妇99| 99视频日韩| 日韩成人一区二区三区| 欧美猛男做受videos| 91国偷自产一区二区三区成为亚洲经典| 日韩欧美亚洲日产国产| 国产黄色一级大片| 免费精品视频| 久久的精品视频| 黄色正能量网站| **精品中文字幕一区二区三区| 亚洲综合久久久| 日本高清一区| 韩国av电影在线观看| 日韩1区2区3区| 欧美激情伊人电影| 久久日免费视频| 国产一区福利| 69堂国产成人免费视频| 可以免费观看av毛片| av色综合久久天堂av色综合在| 91丝袜美腿高跟国产极品老师 | 日本在线一区二区| 婷婷久久综合九色综合绿巨人 | 96成人在线视频| 日韩人妻精品中文字幕| 国产精品xvideos88| 在线观看精品国产视频| 国产老熟女伦老熟妇露脸| 国产精品高清一区二区| 色成年激情久久综合| 妞干网在线观看视频| 老司机精品视频在线观看6| 久久先锋影音av鲁色资源| 高清免费日韩| av高清一区二区| 免播放器亚洲一区| 国产suv精品一区二区三区88区| 玖玖爱免费视频| 久久精品国产亚洲夜色av网站| 日韩精品免费在线观看| 国产性猛交96| 91国内精品白嫩初高生| 欧美一区二区在线免费观看| 亚洲欧美偷拍另类| 成人精品一区二区三区电影| 色噜噜狠狠成人中文综合| 成年人视频观看| 91福利在线尤物| 亚洲在线成人精品| 久久国产午夜精品理论片最新版本| 思思99re6国产在线播放| 久久久精品天堂| 蜜桃91精品入口| 香蕉视频911| 99热精品一区二区| 狠狠色噜噜狠狠色综合久| www.我爱av| 成人永久aaa| 亚洲中国色老太| 国产999久久久| 国产精品18久久久久久久久| 91av免费看| 亚洲精品国产精品乱码不卡| 国产成人在线观看免费网站| 91黄色国产视频| 亚洲第九十九页| 成人sese在线| 久久国产精品99久久久久久丝袜 | 成人交换视频| 欧美日韩电影在线播放| 亚洲欧美日本一区二区| 精品一区二区三区视频在线播放| 911国产精品| 18深夜在线观看免费视频| 亚洲不卡在线| 亚洲精品黄网在线观看| 瑟瑟视频在线观看| 日韩精品免费| 欧美成aaa人片免费看| 国产亚洲第一页| 国产女优一区| 国产精品尤物福利片在线观看| 中文字幕在线网站| 国产激情一区二区三区桃花岛亚洲| 欧美性猛交xxxxx水多| 日韩视频在线一区| 国产精品视频一区二区三| 亚洲欧美综合| 97精品在线观看| 无码人妻精品一区二区三区蜜桃91 | 一区二区不卡在线| 在线heyzo| 色综合久久久久| 天堂在线一区二区三区| av成人综合| 亚洲欧洲日产国产网站| 亚洲人做受高潮| 国产伊人精品| 国产精品美女999| 亚洲伦理在线观看| 欧美国产日韩a欧美在线观看| 樱空桃在线播放| a欧美人片人妖| 欧美午夜精品久久久久久孕妇 | 日韩在线观看网站| 国产亚洲小视频| 日韩精品乱码av一区二区| 91色视频在线导航| 日本一区高清| 免费视频久久| 91久久精品网| 999久久久精品视频| 亚洲欧美成人vr| 欧美国产中文字幕| 国产一区二区视频免费观看| 99久久精品国产一区二区三区| 一级特黄录像免费播放全99| 国产传媒在线| 欧美一区国产二区| 18精品爽国产三级网站| 亚洲欧美日韩精品一区二区| 亚洲精品免费在线视频| 韩日视频在线| 欧美日韩国产激情| 国产亚洲精品成人a| 久久精品高清| 国产成人激情视频| 天天射天天操天天干| 亚洲视频精选在线| 黄色片在线免费| 欧美一级三级| 久久久久在线观看| 国产黄色小视频在线观看| 国产精品久久影院| 国产精品动漫网站| 日本韩国欧美超级黄在线观看| 久久这里只有精品99| 一区精品在线观看| 国产欧美一区二区精品久导航 | 99精品久久久| 成人91视频| 91精选在线| 欧美一级午夜免费电影| www.av免费| 激情六月婷婷综合| 亚洲图色在线| 亚洲成人av观看| 一区二区三区视频在线| 中文字幕手机在线视频| 久久综合狠狠综合久久综合88| 国产真人做爰毛片视频直播| 中文字幕一区图| 欧美激情第一页xxx| 欧美精品123| 午夜视频免费看| 亚洲电影中文字幕在线观看| 亚洲香蕉中文网| 亚洲九九精品| 精品国产乱码久久久久久88av | 人人做人人澡人人爽欧美| 亚洲av激情无码专区在线播放| 亚洲成av人片在线观看| 久久久午夜精品福利内容| 日韩香蕉视频| 欧美精品一区二区视频| 免费观看成人性生生活片| 国产亚洲免费的视频看| 国产又粗又黄又爽| 亚洲另类在线制服丝袜| 国产香蕉精品视频| 国产精品一区亚洲| 婷婷精品国产一区二区三区日韩| 国产精品99久久久久久董美香 | 福利在线免费视频| 日韩h在线观看| 波多野结衣日韩| 国产精品国产三级国产aⅴ原创| 在线播放av中文字幕| 今天的高清视频免费播放成人| 精品1区2区| 天堂久久午夜av| 久久精品精品电影网| 成人小说亚洲一区二区三区| 欧美日韩色婷婷| 欧美精品日韩在线| 国产成人av电影在线观看| 男人日女人下面视频| 日韩av在线播放网址| 99久热re在线精品996热视频| 在线日韩影院| 久久久av亚洲男天堂| 天天色天天操天天射| 欧美日韩在线电影| 日韩欧美一区二区一幕| 中文字幕精品三区| 欧美做受高潮中文字幕| 日韩精品一级中文字幕精品视频免费观看 | mm131亚洲精品| 亚洲小说欧美另类社区| 日韩欧美精品久久| 91精品久久久久久综合五月天| 欧美亚洲激情视频| av在线免费观看网址| 亚洲欧美日韩综合| 性中国xxx极品hd| 欧美亚洲动漫制服丝袜| 欧美人与禽zozzo禽性配| 日本一区二区三区国色天香| 亚洲一二三四五| 狠狠网亚洲精品| 成人羞羞国产免费网站| 一区二区中文字| 日韩欧美亚洲在线| 国产精品白丝一区二区三区| 成人在线播放av| 亚洲www啪成人一区二区| 欧美亚洲国产精品| 97人人在线视频| 久久视频中文字幕| 国产精品免费播放| 亚洲欧美国产日韩天堂区| www黄色在线观看| 欧美日韩一区二区欧美激情| 特黄视频免费看| 亚洲国产欧美在线| 国产女人18水真多毛片18精品| 国产日韩欧美精品一区| 懂色av粉嫩av蜜乳av| 国产成人99久久亚洲综合精品| 亚洲人视频在线| 日韩精品一级中文字幕精品视频免费观看 | 国产成人高清精品| 在线精品播放av| 精品无吗乱吗av国产爱色| 亚洲精品videossex少妇| 高h放荡受浪受bl| 日韩欧美aaaaaa| 国产乱码久久久| 欧美精三区欧美精三区| 中文字幕资源网| 欧美日韩激情一区二区| 中文字幕网址在线| 欧美在线免费播放| 久久精品五月天| 日本高清无吗v一区| 中文字幕av影院| 日韩欧美主播在线| 无码人妻精品一区二区三区9厂| 欧美性xxxxxxx| 高清乱码免费看污| 欧美最新大片在线看| 国语对白做受69按摩| 在线观看91视频| 中文字幕永久在线| 欧美日韩大陆一区二区| 国产又粗又猛又爽又黄视频| 在线成人av网站| 国产aⅴ一区二区三区| 日韩色视频在线观看| 亚洲精品久久久狠狠狠爱| 亚洲成人黄色在线观看| 手机看片1024日韩| 亚洲精选一区二区| 不卡在线视频| 久久久av网站| 91高清视频在线观看| 全亚洲最色的网站在线观看| abab456成人免费网址| 国产精品国产三级国产专播精品人| www.国产精品| 亚洲一区二区中文| 国产毛片精品| 日韩一区二区三区资源| 91精品国产调教在线观看| 免费在线看黄色片| 免费日韩av片| 想看黄色一级片| av中文一区二区三区| 自拍偷拍亚洲天堂| 亚洲日本在线天堂| 中文字幕一区二区三区精品| 色激情天天射综合网| 国产一区二区三区在线观看| 欧美精品一区在线观看| 国产污视频在线| 欧美人与物videos| 婷婷综合六月| 1区1区3区4区产品乱码芒果精品| 欧美a一欧美| 日日噜噜噜夜夜爽爽| 亚洲欧洲午夜| 成人免费在线观看视频网站| 国产高清不卡二三区| 欧美日韩高清丝袜| 亚洲激情六月丁香| 日本视频网站在线观看| 日韩一卡二卡三卡国产欧美| 神马久久高清| 久久激情视频免费观看| 欧美男男激情videos| 成人在线精品视频| 免费观看久久av| 国产免费裸体视频| 蜜臀精品一区二区三区在线观看 | 欧美成年人网站| 欧美freesex| 电影午夜精品一区二区三区 | 91美女视频网站| avove在线播放| 欧美视频在线播放| 午夜视频福利在线| 欧美黑人巨大xxx极品| 日本久久一区| 午夜精品美女久久久久av福利| 在线看片欧美| 国产高清av片| 国产精品色呦呦| 黄色免费av网站| 亚洲国产成人精品久久| 男人在线资源站| 国产精品久久久久久一区二区| 老汉色老汉首页av亚洲| 50度灰在线观看| 精品一区精品二区高清| 熟女少妇内射日韩亚洲| 五月激情综合色| 亚洲精品第五页| 欧美国产日韩中文字幕在线| 欧美一区=区三区| 性欧美.com| 日韩中文欧美在线| 欧美大波大乳巨大乳| 欧美特黄级在线| 欧美日韩在线精品一区二区三区激情综 | 欧美色爱综合网| 男女视频在线观看| 欧美有码在线观看| 亚洲婷婷伊人| 欧洲av无码放荡人妇网站| 久久综合九色综合欧美就去吻 | 欧美在线免费播放| 国产中文在线视频| 国产精品久久久久久久久久久新郎| 欧美亚洲tv| av免费在线播放网站| 久久品道一品道久久精品| 91视频免费网址| 亚洲精品乱码久久久久久按摩观| 国产免费拔擦拔擦8x高清在线人| 成人在线视频电影| 樱桃成人精品视频在线播放| 免费观看污网站| 亚洲国产精品久久一线不卡| 亚洲老妇色熟女老太| 国内自拍欧美激情| 少妇久久久久| 好男人www社区| 国产精品美女www爽爽爽| 一二区在线观看| 欧美成人高清视频| av综合网站| 黄色网页免费在线观看| 久久在线免费观看| 亚洲天堂视频在线播放| 日韩中文字幕不卡视频| 欧美三级一区| 人人妻人人添人人爽欧美一区| 91网站最新网址| 中文字幕人妻精品一区| 精品中文字幕乱| 欧美三级自拍| 2025韩国理伦片在线观看| 日韩一区在线播放| 午夜精品久久久久久久99热黄桃 | 亚洲午夜精品久久久中文影院av| 麻豆精品久久精品色综合| 亚洲综合网在线| 日韩av在线精品| 国产精品传媒麻豆hd| 免费特级黄色片| 国产亚洲精品中文字幕| av网站在线免费看| 6080yy精品一区二区三区| 成人久久一区| 亚洲少妇中文字幕| 欧美午夜免费电影| 国产桃色电影在线播放| 日本不卡二区| 国产mv日韩mv欧美| 无码人妻精品一区二区三区不卡| 欧美理论片在线观看| 亚洲福利天堂| 操人视频免费看| 色网综合在线观看| 激情图片在线观看高清国产| 欧美性天天影院| 成人性色生活片免费看爆迷你毛片| 亚洲毛片一区二区三区|