Go遊戲伺服器開發的一些思考(十):goroutine和coroutine
概要
go語言的特色之一就是goroutine。也就是go協程。由於協程這個東西在go語言之前,用到相對比較少,大家對協程的理解程度不一,或有偏差。比如本人剛接觸goroutine時,就對其比較畏懼,因為不知道它到底是如何運作的。因此有必要深入瞭解下什麼是協程,它的今生前世,以及工作原理
前世
作為伺服器端程式設計師,一般來說,都會使用過、或者自己實現過 “通用的非同步任務系統” ,來達成安全方便的多執行緒使用。通常來講,比較典型的會是基於actor模型及回撥的方式制定差異。
這裡我們主要來考察下其不足之處。下面簡單的畫一下 任務物件和執行緒間的關係:
------ - ------- -------
| task1 | | task2 | .... | taskn | => thread1
------- ------- -------
------- ------- -------
| taska | | taskb | .... | taskz | => thread2
------- ------- -------
...
------- ------- --- ----
| taskA | | taskB | .... | taskZ | => threadn
------- ------- -------
如圖所示,一般會開n個執行緒來處理,每個任務按一定的策略被投遞到執行緒中執行,任務完成後,觸發非同步回撥。
假設 任務中,有一定比例的任務是IO阻礙的。那麼執行緒在執行這個任務時,會被掛起。導致後面的任務也只能等待。
總結下其不足的地方:
* CPU能力不能達到完全釋放 * IO阻塞任務的數量與執行緒被系統排程成正比 * 任務完成需要回調方式,程式設計上不直觀、比較難受(若沒有前兩條不足,這個大概也不會出現。背鍋俠是也...)
(ps. 協程起源於單CPU單執行緒時代。要解決的問題,同這裡表述的。多執行緒後,為了榨乾CPU處理能力,協程開始用於多執行緒系統,如這裡描述的)
程式設計師的智慧
那麼如何才能把 “非同步任務系統” 做的完美呢。假設能這樣就好了:
* IO阻塞任務,執行到阻塞語句時,系統可以下達它的IO指令又可以把它拎出來,重新插到任務佇列最後。
假設能實現這樣的效果。那麼上述的不足也就不存在了:
* 阻塞的任務因為拎出來了,後續的任務可以繼續歡快的在該執行緒上跑了
* 阻塞的任務因為拎出來了,執行緒也不會被阻塞,也就不會被系統排程出去了
那麼如何才能做到。聰明的程式設計師很快找到了解決方案:
* 將IO阻塞的API hook掉,換成非同步實現
* 模仿作業系統執行緒的排程方法,實現任務的切出切進
這裡點下,為什麼需要 “實現任務的切出切進”。由於把IO阻塞的API hook掉,換成非同步實現。如果讓該任務繼續執行的話,就會改變該任務的流程。因此必須切出去。等再次切進來時,檢查IO事件是否已經到了。到了則如同 IO阻塞完畢,繼續執行任務流程。否則再次切出。
今生
於是很多產品出來了。
首先是作業系統,推出了使用者態 上下文切換API
作業系統 | API |
---|---|
window | 纖程(Fiber系列API) |
linux | ucontext系列API |
其他 | 大廠直接自己彙編搞定,高階定製(比如go …) |
然後 很多開源庫。這裡介紹幾個有名的
開源庫 | 介紹 |
---|---|
boost | boost::context、boost::coroutine。僅跨平臺提供協程基礎功能。 |
libco | 騰訊的協程庫。明顯的閹割版放出。沒有提供上層封裝使用層程式碼 |
libgo | 魅族的協程庫。和goroutine使用功能上無限接近。 |
libtask | goroutine前身。goroutine一出,大家驚呆了,原來封裝的好,也可以這麼好用。 |
順便吐槽下,翻下libco、libgo、libtask,會發現程式碼中只有task。而到了市面上則變成了 協程(coroutine)。玩起概念來…
goroutine、libgo、libco 比較
開源庫 | HOOK情況 | 協程大小 | 功能完成度(goroutine用法為參照) | 程式碼可讀性 |
---|---|---|---|---|
goroutine | 全部hook | 8K開始,動態增加,分段棧實現 | 100% | go程式碼太龐大,還沒找到… |
libco | socket系列API | 可制定大小,共享棧實現 | 最基本的協程功能 | 程式碼量小,比較難看,沒看 |
libgo | socket系列API | 可制定大小,沒有特殊實現 | 接近100% | 程式碼量小,可讀性很高,基本看懂 |
(ps.據libgo的作者說,libgo協程棧用了 “os的虛擬記憶體機制,是動態增長的”。待考究優略 )
C++框架新的可能
C++ libgo庫設計的非常棒,還原 goroutine 程度 接近100%(當然這裡指的是用法。穩定性、可靠性還需很多人、產品的檢驗)。因此讓C++引擎具備實用的cocoutine系統已經是完全可行。