Lua 协同程序(coroutine)-类型线程

2019-05-04 23:57|来源: 网路

Lua 协同程序(coroutine)


什么是协同(coroutine)?

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

协同是非常强大的功能,但是用起来也很复杂。

线程和协同程序区别

线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。

在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。

协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

基本语法

方法 描述
coroutine.create() 创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用
coroutine.resume() 重启coroutine,和create配合使用
coroutine.yield() 挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果
coroutine.status() 查看coroutine的状态
注:coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序
coroutine.wrap() 创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复
coroutine.running() 返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号

以下实例演示了以上各个方法的用法:

-- coroutine_test.lua 文件
co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
 
print("----------")
 
co = coroutine.wrap(
    function(i)
        print(i);
    end
)
 
co(1)
 
print("----------")
 
co2 = coroutine.create(
    function()
        for i=1,10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2))  --running
                print(coroutine.running()) --thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
 
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
 
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
 
print("----------")

以上实例执行输出结果为:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868    false
suspended
thread: 0x7fb801c04c88    true
----------

coroutine.running就可以看出来,coroutine在底层实现就是一个线程。

当create一个coroutine的时候就是在新线程中注册了一个事件。

当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。

接下来我们分析一个更详细的实例:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("第二次协同程序执行输出", r)
    local r, s = coroutine.yield(a + b, a - b)  -- a,b的值为第一次调用协同程序时传入
     
    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入
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")) -- cannot resume dead coroutine
print("---分割线---")

以上实例执行输出结果为:

第一次协同程序执行输出    1    10
foo 函数输出    2
main    true    4
--分割线----
第二次协同程序执行输出    r
main    true    11    -9
---分割线---
第三次协同程序执行输出    x    y
main    true    10    结束协同程序
---分割线---
main    false    cannot resume dead coroutine
---分割线---

以上实例接下如下:

  • 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;

  • 协同程序运行;

  • 运行到yield语句;

  • yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)

  • 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)

  • yield返回;

  • 协同程序继续运行;

  • 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine

resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。


生产者-消费者问题

现在我就使用Lua的协同程序来完成生产者-消费者这一经典问题。

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()

以上实例执行输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
……

相关问答

更多
  • Corona包含LuaSockets ,可以让你进行异步套接字通信,如下所示。 Corona contains LuaSockets which will let you do asynchronous socket communication, as seen here.
  • 正如评论中已经提到的,C API中none使用任何值来检查是否没有值。 请考虑以下脚本: function foo(arg) print(arg) end foo(nil) --> nil foo() --> nil 在Lua中,您可以使用select('#', ...)来获取传递给foo的参数数量,并且使用C API可以检查用户是否完全不提供参数(使用lua_isnone )。 考虑下面的小C库,它类似于type ,但它可以识别,如果没有给出参数: #include ...
  • pcall什么都不做,因为没有引发错误:执行文件中的错误不会引起os.execute调用者的os.execute 。 您需要测试os.execute的返回代码并根据需要引发错误。 pcall does nothing because no error is raised: an error in the executed file does not raise an error in the caller of os.execute. You need to test the return code of ...
  • 其中(Ret)应该包含Lua传递给yield的值。 Luabind只支持单个返回值,因此它只返回传递给coroutine.yield的第一个值。 如果函数返回而不是调用yield会发生什么? resume_function是否返回函数的返回值? 是的,你得到了它的回报价值。 如果你事先不知道哪个(或多少个)参数将被传递给yield,你应该如何使用这个函数? 例如,如果有多个可能的屈服函数,则函数可以调用。 这取决于你; 他们是你的职责。 你必须开发关于屈服函数作为参数接收什么的约定,以及恢复协程提供的函数。 ...
  • 正如Lua书中所述,协同程序确实是合作的。 ANSI C没有解决线程问题,所以在Lua中没有“本地”方式来执行抢占式多线程,就像在C中没有“本地”方式那样。相反,你将不得不依赖对底层操作的调用系统。 Lua wiki讨论了两种维护线程状态的方法。 我还发现了一篇关于协同程序的博客文章 ,其中详细介绍了两种方法中的一种(尽管他自己并没有进入先发制人的线程)。 也许值得注意的是Lua的书中说,“我们认为多线程对Lua来说不是一个好主意。” 如果您愿意,可以在第30章中阅读更多关于他们关注的问题。 Corouti ...
  • 如前所述,脚本有几个问题妨碍您获得您想要的内容: os.execute("...")被阻塞,直到命令完成,并在你的情况下它不完成(因为它运行一个无限循环)。 解决方案:您需要使用类似io.popen()而不是os.execute()从您的进程中分离该进程 co = coroutine.create( startServer() )不会在你的情况下创建一个协同程序。 coroutine.create调用接受函数引用,并将其传递给startServer调用的结果,即nil 。 解决方案:使用co = corou ...
  • 通过推送目标地址启动协同处理,然后每个协程交换机将当前PC与堆栈顶部交换,最终必须弹出以终止协同处理。 Coroutining is initiated by pushing the target address, then each coroutine switch exchanges the current PC with the top of the stack, which eventually has to get popped to terminate the coroutining.
  • coroutine.create(b.info()) 在恢复co 之前调用b.info 。 您需要传递函数,而不是函数调用 ,如 local co = coroutine.create(b.info) coroutine.resume(co) -- prints nothing coroutine.resume(co) -- prints b, b2 要么 co = coroutine.wrap(b.info) co() co() coroutine.create(b.inf ...
  • 除了5.2的源代码之外什么都没有看来,似乎from仅用于在恢复期间正确计算嵌套C调用的数量。 L->nCcalls = (from) ? from->nCcalls + 1 : 1; 和 lua_assert(L->nCcalls == ((from) ? from->nCcalls : 0)); coroutine.resume的实现似乎以这种方式使用它。 它使用正在恢复它的主线程的from值恢复coroutine线程上的协同程序。 status = lua_resume(co, L, narg); ...
  • 正如Lua要求的man页面所描述的 ,它在路径中搜索文件。 这个路径可以在C中定义。看看这篇文章: “从C ++ / C设置全局LUA_PATH变量” require函数非常实用,可以加载.lua文件中定义的模块和库。 As decribed by the Lua require man page, it searches for the file in a path. This path can be defined in C. Have a look to this post : "Setting th ...