2014-11-25 83 views
4

我試圖在copas內使用redis-lua庫。它需要一些補丁。 一個問題是redis-lua定義了一些迭代器作爲協同程序,但是這些迭代器執行的網絡操作可以是yieldLua嵌套協程

因此,coroutine.yield用於兩個非常不同的事情:對於迭代器和copas。由於網絡調用嵌套在迭代器中,網絡收益被迭代器的coroutine.wrap攔截,而不是被copas攔截。

下面的例子說明這個問題:

local function iterator() 
    for i = 1, 2 do 
    if i == 2 then coroutine.yield() end -- network yield 
    coroutine.yield() -- iterator yield 
    end 
end 
local citerator = coroutine.wrap (iterator) 

local function loop() -- use of the iterator within a copas thread 
    while citerator() do end 
end 
local cloop = coroutine.create (loop) 

while coroutine.resume (cloop) do end -- same as copas loop, executes the cloop thread 

是否有一個「標準」解決了這個問題,仍然允許使用協同程序進行迭代器?

我能夠通過「標記」yield(見下文)做出一個小例子,但它與現有代碼不兼容。我可以不修改代碼copas,但必須更新redis-lua中的迭代器。

local function wrap (f, my_tag) 
    -- same as coroutine.wrap, but uses my_tag to yield again 
    local co = coroutine.create (f) 
    return function() 
    local t = table.pack (coroutine.resume (co)) 
    local code = t [1] 
    local tag = t [2] 
    table.remove (t, 1) 
    table.remove (t, 1) 
    if tag == nil then 
     return 
    elseif my_tag == tag then 
     return table.unpack (t) 
    else 
     coroutine.yield (tag, table.unpack (t)) 
    end 
    end 
end 

local Iterator = {} -- tag for iterator yields 
local Network = {} -- tag for network yields 

local function iterator() 
    for i = 1, 2 do 
    if i == 2 then coroutine.yield (Network, i) end 
    coroutine.yield (Iterator, i) 
    end 
end 

local citerator = wrap (iterator, Iterator) 

local function loop() 
    while citerator() do end 
end 

local cloop = wrap (loop, Network) 

while cloop() do end 

有沒有更好的解決方案?

回答

5

Lua協同程序總是屈服於最後一次從它們恢復的線程。 Copas套接字函數期望回到Copas事件循環,但相反,它們被用於實現redis-lua迭代器的協程卡住。不幸的是,除了修改redis-lua迭代器的代碼之外,你可以做些什麼來解決這個問題。之所以沒有人這樣做的原因是,除非Lua 5.2(LuaJIT可以做到這一點),否則甚至不可能從迭代器函數中產生(迭代器在redis-lua中的產量很好,因爲它們永遠不會離開迭代器函數,但是不能像Encas套接字函數試圖做的那樣產生超出for的循環)。

有關使用標記值區分迭代器產出與其餘部分的想法很好。您只需確保您將所有不是用於迭代器函數的收益傳遞到協程一級,包括任何參數/返回值coroutine.yieldcoroutine.resume(在調用coroutine.wrap ed函數時,後者是隱含的)。

更具體地說,如果您在Redis的-LUA有這樣的代碼:

-- ... 
return coroutine.wrap(function() 
    -- ... 
    while true do 
    -- ... 
    coroutine.yield(some_values) 
    end 
end) 

將其更改爲:

-- ... 
local co_func = coroutine.wrap(function() 
    -- ... 
    while true do 
    -- ... 
    coroutine.yield(ITERATOR_TAG, some_values) -- mark all iterator yields 
    end 
    return ITERATOR_TAG -- returns are also intended for the iterator 
end) 
return function() 
    return pass_yields(co_func, co_func()) -- initial resume of the iterator 
end 

ITERATOR_TAGpass_yields功能的地方去附近的redis.lua頂部:

local ITERATOR_TAG = {} -- unique value to mark yields/returns 

local function pass_yields(co_func, ...) 
    if ... == ITERATOR_TAG then -- yield (or return) intended for iterator? 
    return select(2, ...) -- strip the ITERATOR_TAG from results and return 
    else 
    -- pass other yields/resumes back and forth until we hit another iterator 
    -- yield (or return); using tail recursion here instead of a loop makes 
    -- handling vararg lists easier. 
    return pass_yields(co_func, co_func(coroutine.yield(...))) 
    end 
end 

AFAIK,r edis-lua開發人員計劃在今年年底之前給另一個版本添加標籤,所以他們可能會對pull請求感激不盡。

+0

使用你的貢獻,我寫了一個[小模塊](https://github.com/saucisson/nested-coroutines)來(幾乎)透明地替換lua的'coroutine'。它通過[lua測試套件](http://www.lua.org/tests/5.2/)。唯一的變化是要求協程模塊爲:'local coroutine = require「coroutine.make」()'。 – 2014-11-26 16:15:58

+0

@AlbanLinard好主意。我選擇了一種不同的方法來實現這種功能的自動化:我編寫了一個函數,它使用回調函數自動轉換迭代函數(如舊的['table.foreach'](http://www.lua.org/manual/5.0/manual .html#5.4)從Lua 5.0轉換爲for循環迭代器,並使用協程和標記的yield。 – siffiejoe 2014-11-26 17:49:01

0

在第一個示例中,您使用的是wrap(loop)。我想這是COPAS' wrap,因爲在此代碼COPAS沒有參考...

但是,你應該copas.wrap()一個插座,但你的loop是一個功能!

請參閱copas documentation for a good introduction

+0

我編輯了第一個代碼清單。 'wrap'實際上是一個'coroutine.create',而對cloop的調用現在是'coroutine.resume'。 – 2014-11-26 08:08:59