一,lua協程簡介
協程(coroutine),意思就是協作的例程,最早由Melvin Conway在1963年提出並實現。跟主流程序語言中的線程不一樣,線程屬於侵入式組件,線程實現的系統稱之為搶占式多任務系統,而協程實現的多任務系統成為協作式多任務系統。線程由於缺乏yield語義,所以運行過程中不可避免需要調度,休眠掛起,上下文切換等系統開銷,還需要小心使用同步機制保證多線程正常運行。而協程的運行指令系列是固定的,不需要同步機制,協程之間切換也只涉及到控制權的交換,相比較線程來說是非常輕便的。不過同一時刻可以有多個線程運行,但卻只能有一個協程運行。
協程具有兩個非常重要的特性:
1. 私有數據在協程的間斷式運行期間一直有效
2. 協程每次yield后讓出控制權,下次被resume后從停止點開始繼續執行
通俗的說,比較像一個帶有靜態數據而且具有多個進入點和返回點的函數,下面通過一個簡單例子看看這個性質:
co = coroutine.create( function(a) print("a = "..a) c = coroutine.yield(a+1) print("c = "..c) return c*2 end ) _, b = coroutine.resume(co, 1) print("b = "..b) _, d = coroutine.resume(co, b ) print("d = "..d)
運行結果:
a = 1
b = 2
c = 2
d = 4
協程co中除了一般函數具有的進入點(函數入口處)和返回點(函數結尾),在yield出還有一個進入點和返回點。主程序第一次resume時將1傳給參數a,運行到yield時,協程返回控制權給主程序,並通過yield的參數提供返回值,於是b=a+1,再次resume時,主程序通過參數b將數據傳給co的變量c,從而從第二個入口點進入函數,最后返回值c*2傳給d。
resume/yield語義實現的協程屬於非對稱協程,在非對稱協程中,調用者和被調用者的關系是固定的,調用者通過resume將控制流轉到被調用者,被調用者通過yield只能返回到調用者,而不能返回到其他協程。比如A resume B resume C, C yield只能到B,而不能到A或其他協程。
世間萬物都是對立又統一的,既然存在非對稱協程,當然就存在對稱協程。對稱協程只有一個語義可以將控制流直接轉到目的協程。
非對稱協程和對稱協程的表達能力是一樣的,lua中只實現了非對稱協程,一個重要原因是lua是c實現的,非對稱協程的調用與被調用關系與c的函數調用非常類似,方便lua和c的擴展編程。
二,協程實戰
下面例子利用協程實現經典的生產者-消費者模型。
count = 10 productor = coroutine.create( function () i = 0 while(true) do i = i+1 coroutine.yield(i) end end ) consumer = coroutine.create( function(co) n = 1 while(n<count) do _, v = coroutine.resume(co) print(v) n = n+1 end end ) coroutine.resume(consumer, productor)
consumer啟動productor,productor產生一個item后通過yield傳回給consumer,然后掛起等待consumer下次resume,非常簡單直觀。
協程的另一個重要作用是作為迭代器,依次訪問數據結構的元素。下面代碼展示了先序訪問二叉樹的方法。
l = { v = 1, left = nil, right = nil, } r = { v = 2, left = nil, right = nil, } root = { v = 3, left = l, right = r, } preorder = function(root) if(root == nil) then return end coroutine.yield(root.v) preorder(root.left) preorder(root.right) end preco = coroutine.create(preorder) view = function(co, root) state, v = coroutine.resume(co, root) if(state == false) then return end print(v) while(true) do state, v = coroutine.resume(co) if(state == false or v == nil) then return end print(v) end end view(preco, root)
對於coroutine, 這里介紹了一個非常輕量級協程的實現原理,雲風通過uconext實現了類似於lua的協程庫,有興趣的同學可以研究研究。
reference:
《coroutines in C》