標籤:style http 使用 io strong 資料 for ar
前言
協同程式與線程差不多,也就是一條執行序列,擁有自己獨立的棧、局部變數和指令指標,同時又與其它協同程式共用全域變數和其它大部分東西。從概念上講,線程與協同程式的主要區別在於,一個具有多個線程的程式可以同時運行幾個線程,而協同程式卻需要彼此協作的運行。就是說,一個具有多個協同程式的程式在任意時刻只能運行一個協同程式,並且正在啟動並執行協同程式只會在其顯式地要求掛起時,它的執行才會暫停。
協同程式基礎
Lua將所有關於協同程式的函數放置在一個名為“coroutine”的table中。函數create用於建立新的協同程式,它只有一個參數,就是一個函數。該函數的代碼就是協同程式需要執行的內容。create會返回一個thread類型的值,用以表示新的協同程式,一般create的參數是一個匿名函數,例如以下代碼:
| 1 |
local co = coroutine.create(function () print("Hello WOrld") end) |
一個協同程式可以有四種不同的狀態:掛起(suspended)、運行(running)、死亡(dead)和正常(normal)。當新建立一個協同程式時,它處於掛起狀態,言外之意就是,協同程式不會在建立它時自動執行其內容,我們可以通過函數status來檢查協同程式的狀態。
| 12 |
local co = coroutine.create(function () print("Hello WOrld") end) print(coroutine.status(co)) -- suspended |
函數coroutine.resume用於啟動或再次啟動一個協同程式的執行,並將其狀態由掛起改為運行:
| 123 |
local co = coroutine.create(function () print("Hello WOrld") end) print(coroutine.status(co)) -- suspended coroutine.resume(co) -- Hello World |
上面的代碼中,我調用了resume函數,將協同程式co由suspended改為running狀態,當列印了Hello World之後,協同程式co就處於死亡狀態。
到目前為止,協同程式就是一種函數調用。其實,協同程式的真正強大之處在於函數yield的使用上,該函數可以讓一個運行中的協同程式掛起,而之後可以再恢複它的運行,例如以下代碼:
| 123456789101112131415161718192021222324252627282930313233 |
local co = coroutine.create(function () for i = 1, 10 do print("co", i) coroutine.yield() end end) -- 列印初始狀態 print(coroutine.status(co)) -- suspended -- 喚醒協同程式co coroutine.resume(co) -- 列印co 1 -- 列印協同程式的狀態 print(coroutine.status(co)) -- suspended -- 再次喚醒協同程式co coroutine.resume(co) -- 列印co 2 -- 列印協同程式的狀態 print(coroutine.status(co)) -- suspended coroutine.resume(co) -- 列印co 3 coroutine.resume(co) -- 列印co 4 coroutine.resume(co) -- 列印co 5 coroutine.resume(co) -- 列印co 6 coroutine.resume(co) -- 列印co 7 coroutine.resume(co) -- 列印co 8 coroutine.resume(co) -- 列印co 9 coroutine.resume(co) -- 列印co 10 coroutine.resume(co) -- 什麼都不列印 print(coroutine.status(co)) -- dead coroutine.resume(co) |
當在協同程式的執行中發生任何錯誤,Lua是不會顯示錯誤訊息的,而是將執行權返回給resume調用。當coroutine.resume的第一個傳回值為false時,就表明協同程式在運行過程中發生了錯誤;當值為true時,則表明協同程式運行正常。
當一個協同程式A喚醒另一個協同程式B時,協同程式A就處於一個特殊狀態,既不是掛起狀態(無法繼續A的執行),也不是運行狀態(是B在運行)。所以將這時的狀態稱為“正常”狀態。
Lua的協同程式還具有一項有用的機制,就是可以通過一對resume-yield來交換資料。在第一次調用resume時,並沒有對應的yield在等待它,因此所有傳遞給resume的額外參數都視為協同程式主函數的參數。如下述代碼:
當協同程式中沒有yield時,第一次調用resume,所有傳遞給resume的額外參數都將視為協同程式主函數的參數,如以下代碼:
| 12345 |
local co = coroutine.create(function (a, b, c) print("co", a, b, c) end) coroutine.resume(co, 1, 2, 3) -- co 1 2 3 |
當協同程式中存在yield時,一切就變的複雜了,先來分析一下這個流程:
1. 調用resume,將協同程式喚醒;
2. 協同程式運行;
3. 運行到yield語句;
4. yield掛起協同程式,第一次resume返回(注意:此處yield返回,參數是resume的參數);
5. 第二次resume,再次喚醒協同程式(注意:此處resume的參數中,除了第一個參數,剩下的參數將作為yield的參數);
6. yield返回;
7. 協同程式繼續運行;
此處從其它部落格中借鑒的一部分代碼,可以說明上面的調用流程:
| 123456789101112131415161718192021222324 |
function foo (a) print("foo", a) -- foo 2 return coroutine.yield(2 * a) -- return 2 * a end co = coroutine.create(function (a , b) print("co-body", a, b) -- co-body 1 10 local r = foo(a + 1) print("co-body2", r) local r, s = coroutine.yield(a + b, a - b) print("co-body3", r, s) return b, "end"end) print("main", coroutine.resume(co, 1, 10)) -- true, 4 print("------") print("main", coroutine.resume(co, "r")) -- true 11 -9 print("------") print("main", coroutine.resume(co, "x", "y")) -- true 10 end print("------") print("main", coroutine.resume(co, "x", "y")) -- false cannot resume dead coroutine print("------") |
輸出結果如下:
| 1234567891011121314 |
>lua -e "io.stdout:setvbuf ‘no‘" "test.lua"co-body 1 10 foo 2 main true 4 ------ co-body2 r main true 11 -9 ------ co-body3 x y main true 10 end ------ main false cannot resume dead coroutine ------ >Exit code: 0 |
resume和yield的配合強大之處在於,resume處於主程中,它將外部狀態(資料)傳入到協同程式內部;而yield則將內部的狀態(資料)返回到主程中。
生產者-消費者問題
現在我就使用Lua的協同程式來完成生產者-消費者這一經典問題。生產者生產東西,消費者消費生產者生產的東西。
| 1234567891011121314151617181920212223242526272829 |
local newProductor function productor() local i = 0 while true do i = i + 1 send(i) -- 將生產的物品發送給消費者 end end function consumer() while true do local i = receive() -- 從生產者那裡得到物品 print(i) end end function receive() local status, value = coroutine.resume(newProductor) return value end function send(x) coroutine.yield(x) -- x表示需要發送的值,值返回以後,就掛起該協同程式 end -- 啟動程式 newProductor = coroutine.create(productor) consumer() |
來源網址:http://www.jellythink.com/archives/508