在 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,獲取最新文章信息