在 web 编程中,常常有业务须要在处理请求时作异步操做,好比耗时太长的 IO 操做,等异步执行完成以后再结束请求返回 response 到 client,在这个过程当中 client 和 server 一直保持着链接不释放,也就是当前请求在从 client 的角度看一直处于阻塞状态,直到请求结束。javascript
之因此称之为异步,最重要的特征就是 server 能够继续处理其余 request 而不被阻塞。html
不一样语言在处理这种异步场景的方式是大相径庭的,常见的处理策略有:消息共享(异步任务队列)、多线程多进程、event(linux signals,nodejs event loop)、协程 coroutine(返回 Future、Promise 表明程序执行的将来状态),其中 coroutine 是应用最普遍的,这也是今天此篇的主题。java
什么是 coroutine?简单来讲就是一段能够在特定时刻自由被 suspend、execute、kill 的 program。程序对 coroutine 的控制就像操做系统对 process 的控制,可是代价要低廉的多。这也是不少语言都支持用 coroutine 的方式进行异步操做的一个重要缘由,其中就包括 Golang、Python、JavaScript(ES6)、Erlang 等。node
Talk is cheap, show me your code. 在此咱们用一个很是简单的 web chat demo app 来一块儿表一表 Golang、Python、Nodejs 中的异步。python
Demo 只是为了说明 coroutine 在不一样语言是如何应用的,于是场景很是简单:一个内容输入框,任意 client 发送的消息都能在其余 client 显示。 linux
项目地址git
两个主要的 API:github
/a/message/new
用于消息发送,在此称之为 message-new/a/message/updates
用于消息接受,在此称之为 message-updateClient 经过 message-update 从 server 端获取最新的消息,若是没有新消息,当次 request 就被挂起,等待新消息的发送,当有新消息来临,获取最新消息以后,断开 connection,必定间隔以后从新请求 server 继续获取新的消息,并重复以前的过程。golang
因为 message-update 从 server 获取消息的时候有可能须要较长时间的等待,server 会一直持有 client 的链接不释放,于是要求来自 message-update client 的请求不能阻塞 server 处理其余请求,而且 message-update 在没有消息到达时须要一直挂起。
Server 处理 message-update 的过程就是一个异步的过程。
Python 中用 yield 来实现 coroutine,可是想要在 web 中实现 coroutine 是须要特别处理,在此咱们用了 tornado 这个支持 asynchronous network 的 web framework 来实现 message-update 的处理。
Tornado 中一个 Future 表明的是将来的一个结果,在一次异步请求过程当中,yield 会解析 Future,若是 Future 未完成,请求就会继续等待。
@gen.coroutine #1
def post(self):
cursor = self.get_argument("cursor", None)
# Save the future returned by wait_for_messages so we can cancel
# it in wait_for_messages
self.future = GLOBAL_MESSAGE_BUFFER.wait_for_messages(cursor=cursor)
messages = yield self.future #2
if self.request.connection.stream.closed():
return
self.write(dict(messages=messages))复制代码
#1
出经过 tornado 特有的 gen.coroutine 让当前请求支持 coroutine,#2
是当前请求等待的将来的执行结果,每一个 message-update client 都经过 GLOBAL_MESSAGE_BUFFER.wait_for_messages 的调用生成一个 future,而后加入消息等待的列表,只要 future 未解析完成,请求会一直挂起,tornado 就是经过 yield 和 future 的配合来完成一次异步请求的。
理解 yield 是如何等待 future 完成的过程其实就是理解 Python generator 如何解析的过程,细节咱们有机会在表。
Golang 天生就在语言层面支持了 coroutine go func()
就能够开启 coroutine 的执行,是否是很简单,是否是很刺激,比起 tornado 必须特别处理要赏心悦目的多,并且 go 自带的 net/http
包实现的 http 请求又天生支持 coroutine,彻底不须要相似 tornado 这种第三方 library 来支持了(此处为 Python 2 )。Golang 比 Python 更牛逼的地方在于支持 coroutine 之间使用 channel 进行通讯,是否是更刺激。
func MessageUpdatesHandler(w http.ResponseWriter, r *http.Request) {
client := Client{id: uuid.NewV4().String(), c: make(chan []byte)} #1
messageBuffer.NewWaiter(&client) #2
msg := <-client.c //挂起请求等待消息来临 #3
w.Header().Set("Content-Type", "application/json")
w.Write(msg)
}复制代码
#1
为每一个 client 生成 一个惟一的身份和 channel,而后 client 加入消息 #2
等待列表等候消息的来临,#3
就是挂起请求的关键点:等待 channel 的消息。Channel 的通讯默认就是阻塞的,即当前 message-update 这个 coroutine 会被 #3
的等待而挂起不会执行,也就达到了 client 链接不能断的要求。
Nodejs 天生异步,经过 callback 来完成异步通知的接收和执行。为了演示方便咱们用了 express ,在 express 中若是一个请求不主动调用 res.end
或 res.send
、res.json
请求就不会结束。在 nodejs 中请求如何才能知道消息到达了,须要 response?Python 咱们用了 Future,Golang 用了 channel,Nodejs 实现也毫不仅仅只有一种,在此咱们用了事件和 Promise。
Promise 相似 Future,表明的是一次将来的执行,而且在执行完成以后经过 resolve 和 reject 来完成执行结果的通知,then 或 catch 中获取执行结果。经过 Promise 能有效解决 nodejs 中回调嵌套以及异步执行错误没法外抛的问题。
Promise 做为一种规范,nodejs 中有多种第三方库都作了实现,在次咱们用了 bluebird 这个 library。
事件是 nodejs 中经常使用的编程模型,熟悉 JavaScript 的同窗应该很了解了,在此不细表。
app.post('/a/message/updates', function(req, res){
var p = new Promise(function(resolve, reject){
messageBuffer.messageEmitter.on("newMessage", function(data){ #1
resolve(data); #2
});
});
var client = makeClient(uuidv4(), p);
messageBuffer.newWaiter(client);
p.then(function(data){ #3
res.set('Content-Type', 'application/json');
res.json({'messages': data});
});
});复制代码
每一个 message-update client 都会生成一个 Promise,而且 Promise 在消息来临事件 newMessage #1
触发之后执行 Promise 的 resolve #2
来告知当前 client 消息来临。
三种语言实现异步的策略是不尽相同的,其中 Golang 的最容易理解也最容易实现,这彻底得意于 go 天生对 coroutine 的支持以及强大的 channel 通讯。文中 Python 的实现是基于 Python 2,在 Python 3 中 coroutine 的使用有了很大的改善,可是相比 Golang 仍是美中不足。Nodejs 做为天生的异步后端 JavaScript,想要彻底使用 Promise 来发挥其优点仍是须要不少技巧来让整个调用栈都完美支持,不过 ES6 中的 yield,ES7 中的 await/async 对异步的操做都有了很大改善,他们的处理方式神似 Python(听说 ES6 草案的实现就是一群 Python 程序员)。
下次须要异步支持的项目,你会用哪一个语言?
Python 的例子改自 tornado github.com/tornadoweb/…
chat-app 地址 github.com/zhyq0826/ch…
Promise Bluebird bluebirdjs.com/docs/gettin…
tornado tornado.readthedocs.io/en/stable/g…
Golang channel tour.golang.org/concurrency…
扫码关注 wecatch,获取最新文章信息