【深入Lua】理解Lua中最强大的特性-coroutine(协程)

原创
2013/12/21 00:40
阅读数 7.5W

点击进入我的新博客


###coroutine基础

Lua所支持的协程全称被称作协同式多线程(collaborative multithreading)。Lua为每个coroutine提供一个独立的运行线路。然而和多线程不同的地方就是,coroutine只有在显式调用yield函数后才被挂起,同一时间内只有一个协程正在运行。

Lua将它的协程函数都放进了coroutine这个表里,其中主要的函数如下

表格

摘取一段云风的代码来详尽解释协程的工作机制,在这段代码中,展示了main thread和协程co之间的交互:

<!-- lang: lua -->
function foo(a)
	print("foo", a)
	return coroutine.yield(2 * a)
end

co = coroutine.create(function ( a, b )
	print("co-body", a, b)
	local r = foo(a + 1)
	print("co-body", r)
	local r, s = coroutine.yield(a + b, a - b)
	print("co-body", r, s)
	return b, "end"
end)

print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))

下面是运行结果

co-body 1 10
foo 2
main true 4
co-body r
main true 11, -9
co-body x y
main false 10 end
main false cannot resume dead coroutine

###coroutine和subroutine(子例程)的比较

子例程的起始处是唯一的入口点,一旦return就完成了子程序的执行,子程序的一个实例只会运行一次。

但是协程不一样,协程可以使用yield来切换到其他协程,然后再通过resume方法重入**(reenter)到上次调用yield的地方,并且把resume的参数当成返回值传给了要重入(reenter)**的协程。但是coroutine的状态都没有被改变,就像一个可以多次返回的subroutine

协程的精妙之处就在于协作这一概念,下面我们用生产者和消费者问题来演示一下协程的基本应用。注意:下面的伪码是用Lua的思想写的

var q = queue()

生产者的伪码

loop
	while q is not full
		create product
		add the items to q
	resume to consumer

消费者的伪码

loop
	while q is not empty
		consume product
		remove the items from q
	yield

###coroutine的和callback的比较

coroutine经常被用来和callback进行比较,因为通常来说,coroutine和callback可以实现相同的功能,即异步通信,比如说下面的这个例子:

<!-- lang: lua -->
bob.walkto(jane)
bob.say("hello")
jane.say("hello")

看起来好像是对的,但实际上由于这几个动作walkto,say都是需要一定时间才能做完的,所以这段程序如果这样写的话,就会导致bob一边走一边对jane说hello,然后在同时jane也对bob说hello,导致整个流程十分混乱。

如果使用回调来实现的话,代码示例如下:

<!-- lang: lua -->
bob.walto(function (  )
	bob.say(function (  )
		jane.say("hello")
	end,"hello")
end, jane)

即walto函数回调say函数,say函数再回调下一个say函数,这样回调看起来十分混乱,让人无法一下看出这段代码的意义.

如果用coroutine的话,可以使用如下写法:

<!-- lang: lua -->
co = coroutine.create(function (  )
	local current = coroutine.running
	bob.walto(function (  )
		coroutine.resume(current)
	end, jane)
	coroutine.yield()
	bob.say(function (  )
		coroutine.resume(current)
	end, "hello")
	coroutine.yield()
	jane.say("hello")
end)

coroutine.resume(co)

在上述代码中,一旦一个异步函数被调用,协程就会使用coroutine.yield()方法将该协程暂时悬挂起来,在相应的回调函数中加上coroutine.resume(current),使其返回目前正在执行的协程中。

但是,上述代码中有许多重复的地方,所以可以通过将封装的方式将重复代码封装起来

<!-- lang: lua -->
function runAsyncFunc( func, ... )
	local current = coroutine.running
	func(function (  )
		coroutine.resume(current)
	end, ...)
	coroutine.yield()
end

coroutine.create(function (  )
	runAsyncFunc(bob.walkto, jane)
	runAsyncFunc(bob.say, "hello")
	jane.say("hello")
end)

coroutine.resume(co)

这样就不需要改变从前的所有回调函数,即可通过携程的方式解决异步调用的问题,使得代码的结构非常清晰。

展开阅读全文
打赏
8
28 收藏
分享
加载中

引用来自“王选易”的评论

coroutine.resume写错了,是后一个yield
最后一个例子,能不能给个可以运行的代码啊?
感觉你写的代码不对吧。
2016/12/01 17:04
回复
举报
博主最后举的例子只是协程特性的一方面, 即化异步为同步.
还有个并发没讲.
2016/02/19 18:28
回复
举报
王选易博主
coroutine.resume写错了,是后一个yield
2014/04/16 08:16
回复
举报

引用来自“王选易”的评论

引用来自“ChildhoodX”的评论

老王,关于最后那个回调vs协程那部分,我有些异议。按照你的写法,walkto,say等还是用了回调。详细见我的笔记Gist:https://gist.github.com/dabing1022/8066297

恩,但是你这样的写法有一个问题是:你不知道在walkto之后什么时候resume,再调用say函数。。。

我再理解理解
2013/12/21 15:44
回复
举报
王选易博主

引用来自“ChildhoodX”的评论

老王,关于最后那个回调vs协程那部分,我有些异议。按照你的写法,walkto,say等还是用了回调。详细见我的笔记Gist:https://gist.github.com/dabing1022/8066297

恩,但是你这样的写法有一个问题是:你不知道在walkto之后什么时候resume,再调用say函数。。。
2013/12/21 15:34
回复
举报
王选易博主

引用来自“ChildhoodX”的评论

引用来自“王选易”的评论

引用来自“ChildhoodX”的评论

上面是第一个问题,受你下面bob和jane的异步回调的例子影响,我把coroutine.running后面少写了个括号
第二个问题,我加了括号,看下面:
local co = coroutine.create(
  function()
    print("hi")
  end
)


print(type(co))
print(co)
print(coroutine.running())

coroutine.resume(co)
print(coroutine.running())

print(coroutine.status(co))

------------------------------输出:
thread
thread: 0x7f8c1940b560
thread: 0x7f8c19403910  true
hi
thread: 0x7f8c19403910  true
dead

这是是thread了,但疑惑是当前running的thread的地址和创建的thread不一样,这里怎么理解?

当coroutine.running()在coroutine的主函数外调用时,实际上返回的是当前他所在的线程,如果它在主线程,那就返回nil,如果按你的想法,应当这样写:
local co = coroutine.create(
function()
print("hi")
print(coroutine.running())
end
)

在主线程返回的是nil? 我当前机子lua是5.2版本,输出的是主线程的“地址”,犹如上面的我代码输出一样。你的是5.1么?

恩,我的是5.1
2013/12/21 15:23
回复
举报
老王,关于最后那个回调vs协程那部分,我有些异议。按照你的写法,walkto,say等还是用了回调。详细见我的笔记Gist:https://gist.github.com/dabing1022/8066297
2013/12/21 15:02
回复
举报

引用来自“王选易”的评论

引用来自“ChildhoodX”的评论

上面是第一个问题,受你下面bob和jane的异步回调的例子影响,我把coroutine.running后面少写了个括号
第二个问题,我加了括号,看下面:
local co = coroutine.create(
  function()
    print("hi")
  end
)


print(type(co))
print(co)
print(coroutine.running())

coroutine.resume(co)
print(coroutine.running())

print(coroutine.status(co))

------------------------------输出:
thread
thread: 0x7f8c1940b560
thread: 0x7f8c19403910  true
hi
thread: 0x7f8c19403910  true
dead

这是是thread了,但疑惑是当前running的thread的地址和创建的thread不一样,这里怎么理解?

当coroutine.running()在coroutine的主函数外调用时,实际上返回的是当前他所在的线程,如果它在主线程,那就返回nil,如果按你的想法,应当这样写:
local co = coroutine.create(
function()
print("hi")
print(coroutine.running())
end
)

在主线程返回的是nil? 我当前机子lua是5.2版本,输出的是主线程的“地址”,犹如上面的我代码输出一样。你的是5.1么?
2013/12/21 14:49
回复
举报
王选易博主

引用来自“ChildhoodX”的评论

上面是第一个问题,受你下面bob和jane的异步回调的例子影响,我把coroutine.running后面少写了个括号
第二个问题,我加了括号,看下面:
local co = coroutine.create(
  function()
    print("hi")
  end
)


print(type(co))
print(co)
print(coroutine.running())

coroutine.resume(co)
print(coroutine.running())

print(coroutine.status(co))

------------------------------输出:
thread
thread: 0x7f8c1940b560
thread: 0x7f8c19403910  true
hi
thread: 0x7f8c19403910  true
dead

这是是thread了,但疑惑是当前running的thread的地址和创建的thread不一样,这里怎么理解?

当coroutine.running()在coroutine的主函数外调用时,实际上返回的是当前他所在的线程,如果它在主线程,那就返回nil,如果按你的想法,应当这样写:
local co = coroutine.create(
function()
print("hi")
print(coroutine.running())
end
)
2013/12/21 14:20
回复
举报
王选易博主
那啥,我的输出时这样的
thread
thread: 0x7fe2f8409b40
nil
hi
nil
dead
[Finished in 0.0s]
2013/12/21 14:18
回复
举报
更多评论
打赏
15 评论
28 收藏
8
分享
返回顶部
顶部