【问题标题】:Lua nested coroutinesLua 嵌套协程
【发布时间】:2015-01-23 07:03:54
【问题描述】:

我正在尝试在copas 中使用redis-lua 库。它需要一些补丁。 一个问题是redis-lua定义了一些迭代器作为协程,但是这些迭代器执行的网络操作可以yield

所以,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

对于这个问题是否有一个“标准”的解决方案,仍然允许使用协程作为迭代器?

我可以通过“标记”yields(见下文)来制作一个小示例,但它与现有代码不兼容。我可以不修改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

有没有更好的解决方案?

【问题讨论】:

    标签: lua nested coroutine


    【解决方案1】:

    Lua 协程总是屈服于它们被恢复的最后一个线程。 Copas 套接字函数期望返回到 Copas 事件循环,但相反,它们被用于实现 redis-lua 迭代器的协程卡住了。不幸的是,除了更改 redis-lua 迭代器的代码之外,您无能为力。没有人这样做的原因是,在 Lua 5.2(LuaJIT 也可以这样做)之前,它甚至不可能从迭代器函数中产生(redis-lua 中的迭代器产生工作正常,因为它们永远不会离开迭代器函数,但您不能像 Copas 套接字函数尝试做的那样超出 for 循环。

    您关于使用标记值来区分迭代器产量的想法很好。您只需确保将所有不适用于迭代器函数的收益传递给协程上一层,包括coroutine.yieldcoroutine.resume 的任何参数/返回值(后者在调用coroutine.wraped 时是隐含的函数)。

    更具体地说,如果你在 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,redis-lua 开发人员计划在今年年底之前标记另一个版本,所以他们可能会感谢拉取请求。

    【讨论】:

    • 利用你的贡献,我写了一个small module 来(几乎)透明地替换lua 的coroutine 一个。它通过了lua test suite。唯一的变化是要求协程模块为:local coroutine = require "coroutine.make" ()
    • @AlbanLinard 好主意。我选择了一种不同的方法来自动化这类事情:我编写了一个函数,它使用回调自动将迭代函数(如 Lua 5.0 中的旧 table.foreach)转换为使用协程和标记产量的 for-loop 迭代器。跨度>
    【解决方案2】:

    在您的第一个示例中,您使用的是wrap(loop)。我想这是 copas 的wrap,因为这段代码中没有提到 copas...

    然而,你应该copas.wrap()一个socket,但你的loop是一个函数!

    copas documentation for a good introduction

    【讨论】:

    • 我已经编辑了第一个代码清单。 wrap 实际上是 coroutine.create,对 cloop 的调用现在是 coroutine.resume
    猜你喜欢
    • 2011-11-04
    • 1970-01-01
    • 2020-10-09
    • 2013-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-10
    • 1970-01-01
    相关资源
    最近更新 更多