Lua的多工機制——協程(coroutine)(轉)
Lua的多工機制——協程(coroutine)(轉)[@more@] 併發是現實世界的本質特徵,而聰明的電腦科學家用來模擬併發的技術手段便是多工機制。大致上有這麼兩種多工技術,一種是搶佔式多工(preemptive multitasking),它讓作業系統來決定何時執行哪個任務。另外一種就是協作式多工(cooperative multitasking),它把決定權交給任務,讓它們在自己認為合適的時候自願放棄執行。這兩種多工方式各有優缺點,前者固有的同步問題使得程式經常有不可預知的行為,而後者則要求任務具備相當的自律精神。 協程(coroutine)技術是一種程式控制機制,早在上世紀60年代就已提出,用它可以很方便地實現協作式多工。在主流的程式語言(如C++、Java、Pascal等)裡我們很少能看到協程的身影,但是現在不少動態指令碼語言(Python、Perl)卻都提供了協程或與之相似的機制,其中最突出的便是Lua。 Lua語言實現的協程是一種非對稱式(asymmetric)協程,或稱半對稱式(semi-asymmetric)協程,又或乾脆就叫半協程(semi-coroutine)。這種協程機制之所以被稱為非對稱的,是因為它提供了兩種傳遞程式控制權的操作:一種是(重)呼叫協程(透過coroutine.resume);另一種是掛起協程並將程式控制權返回給協程的呼叫者(透過coroutine.yield)。一個非對稱協程可以看做是從屬於它的呼叫者的,二者的關係非常類似於例程(routine)與其呼叫者之間的關係。既然有非對稱式協程,當然也就有對稱式(symmetric)協程了,它的特點是隻有一種傳遞程式控制權的操作,即將控制權直接傳遞給指定的協程。曾經有這麼一種說法,對稱式和非對稱式協程機制的能力並不等價,但事實上很容易根據前者來實現後者。接下來我們就用程式碼來證明這個事實。 --對稱式協程庫coro.lua coro = {} --coro.main用來標識程式的主函式 coro.main = function() end -- coro.current變數用來標識擁有控制權的協程, -- 也即正在執行的當前協程 coro.current = coro.main -- 建立一個新的協程 function coro.create(f) return coroutine.wrap(function(val) return nil,f(val) end) end -- 把控制權及指定的資料val傳給協程k function coro.transfer(k,val) if coro.current ~= coro.main then return coroutine.yield(k,val) else -- 控制權分派迴圈 while k do coro.current = k if k == coro.main then return val end k,val = k(val) end error("coroutine ended without transfering control...") end end 如果暫時還弄不懂上面的程式,沒關係,看看如何使用這個庫後再回頭分析。下面是使用示例: require("coro.lua") function foo1(n) print("1: foo1 received value "..n) n = coro.transfer(foo2,n + 10) print("2: foo1 received value "..n) n = coro.transfer(coro.main,n + 10) print("3: foo1 received value "..n) coro.transfer(coro.main,n + 10) end function foo2(n) print("1: foo2 received value "..n) n = coro.transfer(coro.main,n + 10) print("2: foo2 received value "..n) coro.transfer(foo1,n + 10) end function main() foo1 = coro.create(foo1) foo2 = coro.create(foo2) local n = coro.transfer(foo1,0) print("1: main received value "..n) n = coro.transfer(foo2,n + 10) print("2: main received value "..n) n = coro.transfer(foo1,n + 10) print("3: main received value "..n) end --把main設為主函式(協程) coro.main = main --將coro.main設為當前協程 coro.current = coro.main --開始執行主函式(協程) coro.main() 上面的示例定義了一個名為main的主函式,整個程式由它而始,也因它而終。為什麼需要一個這樣的主函式呢?上面說了,程式控制權可以在對稱式協程之間自由地直接傳遞,它們之間無所謂誰從屬於誰的問題,都處於同一個層級,但是應用程式必須有一個開始點,所以我們定義一個主函式,讓它點燃程式執行的導火線。雖說各個協程都是平等的,但做為程式執行原動力的主函式仍然享有特殊的地位(這個世上哪有絕對的平等!),為此我們的庫專門用了一個coro.main變數來儲存主函式,並且在它執行之前要將它設為當前協程(雖然上面的main實際只是一個普通函式而非一個真正的協程,但這並無太大的關係,以後主函式也被稱為主協程)。示例執行的結果是: 1: foo1 received value 0 1: foo2 received value 10 1: main received value 20 2: foo2 received value 30 2: foo1 received value 40 2: main received value 50 3: foo1 received value 60 3: main received value 70 協程的執行序列是:main->foo1->foo2->main->foo2->foo1->main->foo1->main。 coro.transfer(k,val)函式中k是將要接收程式控制權的協程,而val是傳遞給k的資料。如果當前協程不是主協程,tansfer(k,val)就簡單地利用coroutine.yield(k,val)將當前協程掛起並傳回兩項資料,即程式控制權的下一站和傳遞給它的資料;否則進入一個控制權分派(dispatch)迴圈,該迴圈(重)啟動(resume)k協程,等待它執行到掛起(suspend),並根據此時協程傳回的資料來決定下一個要(重)啟動的協程。從應用示例來看,協程與協程之間似乎是用transfer直接傳遞控制權的,但實際上這個傳遞還是透過了主協程。每一個在主協程裡被呼叫(比較coro.current和coro.main是否相同即可判斷出)的transfer都相當於一個協程管理器,它不斷地(重)啟動一個協程,將控制權交出去,然後等那個協程掛起時又將控制權收回,然後再(重)啟動下一個協程...,這個動作不會停止,除非<1>將(重)啟動的協程是主協程;<2>某個協程沒有提供控制權的下一個目的地。很顯然,每一輪分派迴圈開始時都由主協程把握控制權,在迴圈過程中如果控制權的下一站又是主協程的話就意味著這個當初把控制權交出去的主協程transfer操作應該結束了,所以函式直接返回val從而結束這輪迴圈。對於情況<2>,因為coro.create(f)建立的協程的體函式(body function)實際是function(val) return nil,f(val) end,所以當函式f的最後一條指令不是transfer時,這個協程終將執行完畢並把nil和函式f的返回值一起返回。如果k是這樣的協程,transfer執行完k,val = k(val)語句後k值就成了nil,這被視為一個錯誤,因為程式此時沒法確定下一個應該(重)啟動的協程到底是誰。所以在對稱式模型下,每一個協程(當然主協程出外)最後都必須顯式地將控制權傳遞給其它的協程。根據以上分析,應用示例的控制權的分派應為: 第一輪分派: main->foo1->main->foo2->main->main(結束) 第二輪分派: main->foo2->main->foo1->main->main(結束) 第三輪分派: main->foo1->main->main(結束) 由於可以直接指定控制權傳遞的目標,對稱式協程機制擁有極大的自由,但得到這種自由的代價卻是犧牲程式結構。如果程式稍微複雜一點,那麼即使是非常有經驗的程式設計師也很難對程式流程有全面而清晰的把握。這非常類似goto語句,它能讓程式跳轉到任何想去的地方,但人們卻很難理解充斥著goto的程式。非對稱式協程具有良好的層次化結構關係,(重)啟動這些協程與呼叫一個函式非常類似:被(重)啟動的協程得到控制權開始執行,然後掛起(或結束)並將控制權返回給協程呼叫者,這與計算機先哲們倡導的結構化程式設計風格完全一致。 綜上所述,Lua提供的非對稱式協程不但具有與對稱式協程一樣強大的能力,而且還能避免程式設計師濫用機制寫出結構混亂的程式。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-951983/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 理解Python的協程(Coroutine)Python
- Kotlin 協程一 —— CoroutineKotlin
- Kotlin Coroutine(協程): 二、初識協程Kotlin
- Kotlin Coroutine(協程): 三、瞭解協程Kotlin
- Kotlin Coroutine(協程)簡介Kotlin
- Kotlin coroutine之協程基礎Kotlin
- Kotlin Coroutine(協程) 基本知識Kotlin
- Python中協程(coroutine)詳解Python
- 協程Part1-boost.Coroutine.md
- 【譯】kotlin 協程官方文件(1)-協程基礎(Coroutine Basics)Kotlin
- PHP 多工協程處理PHP
- Unity 協程(Coroutine)原理與用法詳解Unity
- Golang 的 協程排程機制 與 GOMAXPROCS 效能調優Golang
- python網路-多工實現之協程Python
- 小議Python3的原生協程機制Python
- Android NDK——初識協程(Coroutine)和libco的簡單介紹使用Android
- iOS 多工Multitasking、後臺機制解析iOS
- python-多工,簡易的協程gevent的安裝與使用例程Python
- 多機器人協作排程問題機器人
- 雲原生網路代理 MOSN 的多協議機制解析協議
- Pod的排程機制
- Golang語言goroutine協程併發安全及鎖機制Golang
- Android中用Kotlin Coroutine(協程)和Retrofit進行網路請求和取消請求AndroidKotlin
- 多型的機制原理多型
- 多工學習時轉角遇到Bandit老虎機
- golang 多協程的同步方法總結Golang
- 爬蟲之多工非同步協程爬蟲非同步
- Android技術分享| 利用Kotlin協程,多工並行,測試RTM SDK效能AndroidKotlin並行
- 執行緒間的協作機制執行緒
- 併發技術2:多協程
- 一種go協程間記憶體零拷貝的訊息通訊機制Go記憶體
- 一種多協程跑指令碼的寫法指令碼
- JavaScript中的多種進位制與進位制轉換JavaScript
- iOS:利用訊息轉發機制實現多播委託iOS
- Unity熱更學習--Lua指令碼使用C#中的事件、委託和協程Unity指令碼C#事件
- swoole 協程原始碼解讀 (協程的排程)原始碼
- lua課程學習筆記筆記
- Lua OpenResty容器化(考古歷程)REST
- [轉載]Spring AOP的實現機制Spring