Lua语言基础汇总(7) -- 协同程序

前言

协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈、局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停。

协同程序基础

Lua将所有关于协同程序的函数放置在一个名为“coroutine”的table中。函数create用于创建新的协同程序,它只有一个参数,就是一个函数。该函数的代码就是协同程序需要执行的内容。create会返回一个thread类型的值,用以表示新的协同程序,一般create的参数是一个匿名函数,例如以下代码:


1

local co = coroutine.create(function () print("Hello WOrld") end)

一个协同程序可以有四种不同的状态:挂起(suspended)、运行(running)、死亡(dead)和正常(normal)。当新创建一个协同程序时,它处于挂起状态,言外之意就是,协同程序不会在创建它时自动执行其内容,我们可以通过函数status来检查协同程序的状态。


1

2

local co = coroutine.create(function () print("Hello WOrld") end)

print(coroutine.status(co))     -- suspended

函数coroutine.resume用于启动或再次启动一个协同程序的执行,并将其状态由挂起改为运行:


1

2

3

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的使用上,该函数可以让一个运行中的协同程序挂起,而之后可以再恢复它的运行,例如以下代码:


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

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的额外参数都将视为协同程序主函数的参数,如以下代码:


1

2

3

4

5

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. 协同程序继续运行;

此处从其它博客中借鉴的一部分代码,可以说明上面的调用流程:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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

输出结果如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

>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的协同程序来完成生产者-消费者这一经典问题。生产者生产东西,消费者消费生产者生产的东西。


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

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

Lua语言基础汇总(7) -- 协同程序,布布扣,bubuko.com

时间: 2024-08-05 05:31:15

Lua语言基础汇总(7) -- 协同程序的相关文章

[转]Lua语言基础汇总(7) -- 协同程序

前言 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈.局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.从概念上讲,线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行.就是说,一个具有多个协同程序的程序在任意时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显式地要求挂起时,它的执行才会暂停. 协同程序基础 Lua将所有关于协同程序的函数放置在一个名为“coroutine”的table中.函数create

Lua语言基础汇总(13)-- Lua中一些常用的库

前言 这篇文章将会来一些比较轻松的内容,就是简单的介绍一下Lua中几个常用的库.简单的说就是几个API的介绍.所以说,看起来比较容易,也没有多大的分量.就是纯粹的总结.使用库就是为了方便我们的开发,提高开发效率,同时也能保证代码的质量.希望大家以后也不要重复造轮子了. 数学库 数学库(math)由一组标准的数学函数构成.这里主要介绍几个常用的函数,其它的大家可以自行百度解决. 1. 三角函数(sin,cos,tan……) 所有的三角函数都使用弧度单位,可以用函数deg(角度)和rad(弧度)来转

Lua语言基础汇总(12)-- Lua中的面向对象编程

简单说说Lua中的面向对象 Lua中的table就是一种对象,看以下一段简单的代码: 1 2 3 4 5 6 7 8 9 10 11 12 local tb1 = {a = 1, b = 2} local tb2 = {a = 1, b = 2} local tb3 = tb1   if tb1 == tb2 then      print("tb1 == tb2") else      print("tb1 ~= tb2") end   tb3.a = 3 pri

Lua语言基础汇总(8) -- Lua中的元表与元方法

前言 元表对应的英文是metatable,元方法是metamethod.我们都知道,在C++中,两个类是无法直接相加的,但是,如果你重载了“+”符号,就可以进行类的加法运算.在Lua中也有这个道理,两个table类型的变量,你是无法直接进行“+”操作的,如果你定义了一个指定的函数,就可以进行了.那本文就是主要讲的是如何定义这个指定的函数,这个指定的函数是什么?希望对学习Lua的朋友有帮助. Lua是怎么做的? 通常,Lua中的每个值都有一套预定义的操作集合,比如数字是可以相加的,字符串是可以连接

Lua语言基础汇总(4) -- 函数

Lua中的函数和C++中的函数的含义是一致的,Lua中的函数格式如下: 1 2 3 function MyFunc(param)      -- Do something end 在调用函数时,也需要将对应的参数放在一对圆括号中,即使调用函数时没有参数,也必须写出一对空括号.对于这个规则只有一种特殊的例外情况:一个函数若只有一个参数,并且此参数是一个字符串或table构造式,那么圆括号便可以省略掉.看以下代码: 1 2 3 4 5 6 print "Hello World"      

Lua语言基础汇总(10) -- Lua中的环境概念

前言 Lua将其所有的全局变量保存在一个常规的table中,这个table称为“环境”.这种组织结构的优点在于,其一,不需要再为全局变量创造一种新的数据结构,因此简化了Lua的内部实现:另一个优点是,可以像其他table一样操作这个table.为了便于实施这种操作,Lua将环境table自身保存在一个全局变量_G中.例如,我们可以使用以下代码打印当前环境中所有全局变量的名称. 1 for n in pairs(_G) do print(n) end 在你的电脑上运行一下以上代码,看看结果. 全局

[转]Lua语言基础汇总(4) -- 函数

Lua中的函数和C++中的函数的含义是一致的,Lua中的函数格式如下: 1 2 3 function MyFunc(param)      -- Do something end 在调用函数时,也需要将对应的参数放在一对圆括号中,即使调用函数时没有参数,也必须写出一对空括号.对于这个规则只有一种特殊的例外情况:一个函数若只有一个参数,并且此参数是一个字符串或table构造式,那么圆括号便可以省略掉.看以下代码: 1 2 3 4 5 6 print "Hello World"      

Lua语言基础汇总(3) -- 语句

赋值 赋值的基本含义是修改一个变量或一个table中字段的值,这个和其它语言没有多少区别,但是对于Lua,有一个特性,它允许“多重赋值”,也就是一下子将多个值赋予多个变量,例如以下代码: 1 2 3 local x1, x2 = 2, 4 print(x1)     -->2 print(x2)     -->4 在多重赋值中,Lua先对等号右边的所有元素求值,然后才执行赋值,例如以下用法,可以非常简便的交换两个元素的值: 1 2 3 4 local x1, x2 = 2, 4 x1, x2 

Lua语言基础汇总(6)-- 迭代器与泛型for

前言 迭代器就是一种可以遍历一种集合中所有元素的机制,在Lua中,通常将迭代器表示为函数.每调用一次函数,就返回集合中的“下一个”元素.每个迭代器都需要在每次成功调用之后保存一些状态,这样才能知道它所在的位置及如何走到下一个位置,通过之前博文的总结,闭包对于这样的任务提供了极佳的支持.现在我们就用代码来实现一个简单的迭代器. 1 2 3 4 5 6 7 8 9 10 11 12 function values(tb)      local i = 0      return function (