1. 程式人生 > >Go遊戲伺服器開發的一些思考(十):goroutine和coroutine

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系統已經是完全可行。