Lua嵌套的协程

Lua nested coroutines

我正在尝试在copas中使用redis-lua库。它需要一些修补程序。
一个问题是redis-lua将某些迭代器定义为协程,但是这些迭代器执行可以yield

的网络操作。

因此,coroutine.yield用于两个截然不同的事物:用于迭代器和copas。当网络调用嵌套在迭代器中时,网络收益被迭代器的coroutine.wrap拦截,而不是被copas拦截。

以下示例显示了问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中更新迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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协程始终屈服于从其恢复的最后一个线程。 Copas套接字函数希望返回到Copas事件循环,但相反,它们陷入了用于实现redis-lua迭代器的协程。不幸的是,除了更改redis-lua迭代器的代码外,您无能为力。之所以没有人这样做的原因是,直到Lua 5.2(LuaJIT也可以做到),才可能无法从迭代器函数产生(迭代器在redis-lua中产生的结果很好,因为它们永远不会离开迭代器)函数,但您不能像Copas套接字函数那样尝试超越for循环。)

关于使用标记值来区分迭代器收益与其余收益的想法很不错。您只需要确保将所有不用于迭代器函数的yield传递给协程上一级,包括coroutine.yieldcoroutine.resume的任何参数/返回值(当调用coroutine.wrap ed时,后者是隐式的)。函数)。

更具体地说,如果在redis-lua中有这样的代码:

1
2
3
4
5
6
7
8
-- ...
return coroutine.wrap( function()
  -- ...
  while true do
    -- ...
    coroutine.yield( some_values )
  end
end )

您将其更改为:

1
2
3
4
5
6
7
8
9
10
11
12
-- ...
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顶部附近的某个位置:

1
2
3
4
5
6
7
8
9
10
11
12
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开发人员计划在年底之前标记另一个发行版,因此他们可能会对拉取请求表示感谢。


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

但是,您应该copas.wrap()一个套接字,但是您的loop是一个函数!

有关详细介绍,请参阅copas文档。