Contents

——我大概是属于那类如果对行动的意义没有清晰的认识就不能很好的行动的人。

所以太过陌生的东西接触起来除非眼缘非常好,否则稍微枯燥无聊一点的事物就完全提不起兴趣。

Lua的coroutine(协程)就是其中的一件。

它有这么几个特点:

  • API少
  • 简单易用
  • 功能强大

尽管如此,因为我一直弄不明白我什么时候会用到它,一直处于“学了忘”无限循环状态。

鉴于最近难得的空闲,我打算彻底解决这个问题!

一般说来,任何支持协程的语言提供的功能都差不多,所以在Lua上也没有特别另类奇葩的新feature,这是作为我相对熟悉的脚本语言,挖出来侃侃,看上去具象一点,任何纯粹的思维都要借助于某种形式的表象来表达嘛~

首先还是从概念说起,什么是协程,我来简化一下。

根据一般的说法,协程是用户层的程序组件,实现了——一言以蔽之——“串行执行的多道程序流”的功能。即实际的线程还是只有一个,但逻辑模拟了多个线程,并提供了一些方式来进行管理,如如下几个Lua API:

coroutine.create
coroutine.resume
coroutine.running
coroutine.status
coroutine.wrap
coroutine.yield

这些API字面意思就比较好理解其功效,但协程在基本的管理机制上还有一种“约束”的体现——协程的管理是单向控制的,如协程A yield之后,只能通过外部恢复,所以关于协程有这样一种说法——用于进行合作式多任务

关于协程的应用场景,几乎所有的博客和百科给出的“典型”栗子就是实现一个“生产者-消费者”模型。

这些都是以前看的,我也能明白,协程只是相当于把原来多线程之间的相互等待和信号量控制转变为更加直接的程序流切换了嘛~ 然而就算是这样,还是很难把它和我现在的开发任务联系起来,对采用这种方式解决问题的意义也没能弄的太明白。

所以后来我又去翻了翻我们工地处理数据库查询部分的代码,我知道那个地方用到协程。

“生产者-消费者”是个抽象模型,而数据库的应用就具体的多,实际上,这样一来也确实看到了这个地方引入协程的好处。

那么就拿数据库查询这个栗子来说:

通常我们是采用异步处理的方式,

业务逻辑—->DBDriver->……->DBMS->……->DBDriver—->业务逻辑

异步处理逻辑在编码上至少要做这样几件事情:在发起异步请求之后,因为暂时获取不到结果,我们需要将context保存起来,然后等待异步消息到达,查询得到这个context再传给对应的处理函数进行处理。

在C++11中,有了function + lambda 我们也可以方便打包context,并且把异步处理前后的逻辑放一块写,缺陷是语意看起来不够和谐。

而对于协程:

function RemoteDB:Query(sql)
	--...
	RegisterCoroutine(key, self.co)	-- 注册context
	self:SendRequest(sql)		-- 发起异步请求
	return coroutine.yield()	-- 挂起等待异步返回
end
----------------------
function QueryDB(sql)
	--...
	local res = remote_db:Query(sql)
	-- deal with res..
	return res
end

可以清晰看到,经过适当的封装以后,写外部逻辑已经完全不需要操心异步处理的问题了,直接以同步的方式写,机制搭起来之后,“机制”会处理好一切的事情。这点是如function之类的用户层工具无论如何都很难做到像协程这种语言内建工具这么优雅的。

所以说,恰当的使用协程,可以大大减少编写逻辑时需要考虑的问题,从而缓解广大码农的颈椎病问题…

最后再附一个自己写的生产者消费者的栗子:

local tbl = { }

function produce(co_consumer)
	local counter = 0
	while true do
		print('producer:')
		table.insert(tbl, "str"..counter)
		coroutine.resume(co_consumer)
		counter = counter + 1
	end
end

function consume()
	while true do
		print('consumer:')
		if not next(tbl) then
			coroutine.yield()
		end
		print(tbl[1])
		table.remove(tbl, 1)
	end
end

local co_consumer = coroutine.create(consume)
local co_producer = coroutine.create(produce)

coroutine.resume(co_producer, co_consumer)
Contents