這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
這段時間看了一些Go語言相關的東西,發現Go語言的最大特性並行存取模型類似於C++裡面的線程池,正好我們專案服務器也是用的線程池,記錄下。
Go語言的並發單位是語言內建的協程,使用關鍵字go+函數建立一個新的協程,新建立的協程會自動加入到協程調度內容相關的等待調度隊列,一個協程調度上下文對應一個線程,一個協程調度上下文對應多個協程。新加入的協程會動態負載到各個調度上下文,如果所有調度內容相關的平均負載較高時,總調度器會自動建立新的線程和對應的調度上下文用於工作。整體上看,是N個線程:N個調度上下文:M個協程的關係。
我們專案服務器線程架構使用boost::threadpool作為底層,按照配置設定的線程數量啟動threadpool,驅動所有Invoker單元,各個Invoker再驅動持有自己的Service運轉。單個Service可以持有一個或多個Invoker。在threadpool和Service之間加入Invoker層,邏輯更清晰,實現了Service(父)和ServiceExec(子)的概念。
本質上,Go語言的協程和我們的線程池+Service結構都實現的是線程與邏輯體的N對M映照關係,並且邏輯層完全屏蔽掉線程的概念。差別在於,Go語言的執行體是基於協程,協程切換是使用者態切換,而我們的Service間切換是作業系統線程的切換,會有大很多的代價。Go語言是內建支援並發,所以在智能負載這樣的細節上也比C++線程池會更強大。經驗豐富的C++程式員才能合理駕馭的線程池在Go語言裡面就是一個關鍵字的使用,語言帶來的生產力提升真是巨大。
再看線程間通訊,Go語言使用內建的通道(chan)類型,我們項目寫了一套Service間Message通訊,本質上都是基於訊息的通訊模型。
我們使用的是訊息佇列輪詢機制,每個Service持有一個std::list,Service之間發訊息通過ServiceManager中轉,Service在心跳中取出list裡面的訊息並處理,因為是線程間共用變數,所以讀取添加訊息都加鎖。
對於Go語言中帶緩衝通道,在通道的緩衝隊列滿之前,往通道裡面塞資料是非阻塞操作;通道緩衝非空的情況下,從通道取資料也是非阻塞操作,這兩種情況與我們的Message類似,區別在於Go語言的通道在緩衝滿時塞資料和緩衝空時取資料是阻塞操作,也就是說Go語言的通道帶有同步語義,而我們的Message是沒有這個功能的。當然,我覺得作為遊戲伺服器是不怎麼需要線程間同步的,基於輪詢的Message處理機制已經完全夠用。Go語言通道強大在於,每個通道都維護了塞資料協程隊列和取資料協程隊列,這極大了擴充了通道的能力,真正達到了通道將不同協程連通的目的。
看了Go語言之後,真心覺得用來開發網遊伺服器實在是太合適了,協程在並發有優勢,開發效率會比C++提升不少,而執行效率據說是不會有太大下降,並且語言文法都很和我的胃口。