簡單介紹多進程和多線程游戲服務器


首先貼下多進程單線程和單進程多線程的特點:

  多進程:有獨立的地址空間,進程之間不共享內存和變量,但可以通過共享內存實現,每個進程只有一個線程,一般用於單機系統開發。

     多線程:在同一個進程下的所有線程可以共享內存和變量。

   而共同點是,同開辟的進程數/線程數多於系統cpu核數時,無法繼續提高應用的性能。

   而多線程架構的服務器,只要適當將一些任務分出來用新的進程啟動,就可以擴展成分布式架構,使用tcp通信即可。當然多進程也可以這么干,通信方式也是使用tcp。

     而操作系統對於線程的切換是比進程的切換要快。

 

下面先介紹下多進程單線程服務器架構,以單機系統為例:

  先貼架構圖:

  

一個游戲服大概就有這幾個進程。

router:作用如其名,路由。 每個功能進程啟動時,會先連接router,router會給連上來的進程分配一個唯一標識,所有功能進程都是靠這個router進程通信。

login: 登錄服務器,client登錄驗證在這個進程進行。

logic:玩家單人邏輯操作處理進程,login會將登錄的玩家平攤到這些logic上。

global_logic: 全局操作進程,多人玩法的功能,例如戰斗匹配,工會等操作會放在這里進行。

log:游戲日志輸出進程,所有功能進程的日志輸出都先發到這個進程,log進程再輸出到磁盤文件,

db: redis作為內存數據庫,mysql作為數據持久化,其他功能進程取數據都會發送請求到db。

back: 后台進程,集成了一個http服務器,可以處理http請求,這里可以集成一些第三方服務功能,如gm指令。

 

以上每個進程都是單線程,所以無需考慮鎖的問題。

對於每個進程收發數據:

  發數據:直接把 {target_id: data} 發送到router,

  收數據:幀驅動,如100ms主動向router詢問是否有數據,有則取過來進行處理。

單機系統下,如果采用共享內存方式,通信效率將非常高。

所以多進程的服務器架構設計起來還是比較簡單的。

 

再介紹下多線程服務器的架構,這里我想介紹actor模型。

 

一個Actor指的是一個最基本的計算單元。它能接收一個消息並且基於其執行計算。

這個理念很像面向對象語言,一個對象接收一條消息(方法調用),然后根據接收的消息做事(調用了哪個方法)。

Actors一大重要特征在於actors之間相互隔離,它們並不互相共享內存。這點區別於上述的對象。也就是說,一個actor能維持一個私有的狀態,並且這個狀態不可能被另一個

actor所改變。

每個Actor都有一個郵箱,用於接收其他actor發送的消息。

 

這里重點要講下Actor模型的調度是怎樣做的。

Actor模型實際上可以有成千上萬個,但目前一台通用服務器最多只有24核,當然不可能也開成千上萬個線程。

 

我們可以把Actor簡單想象成這樣一個類實例:

class Actor
{
public:
void process_1();
void process_2();
void fetch_msg(); private: int actor_id; string actor_name;
list<msg> msg_queue; }

 每個Actor定義了自己實現的功能(process_1, process_2).

當msg_queue郵箱有消息到來的時候,就調用fetch_msg去獲取這些消息進行處理。

這一步就是靠調度線程來做了。

 

Actor模型的調度實現起碼要有:

  1. 一個位於主線程的Actor隊列,如 global_queue<Actor*> gq, 當某個Actor收到消息時,就會被放進這個gq,等待工作線程進行調度。

      2. n個工作線程,這個就要根據機器的核數來決定開多少個了,例如只是一台雙核的機器,那么開一個就好了,開多了就會浪費時間在線程切換上,得不償失。

每個工作線程做的事件很簡單,向主線程詢問任務,獲取任務,處理任務,然后又繼續詢問,大致如下:

while(true)
{
task_list = fetch_task();
process_task(task_list);
}

 所以一個Actor的創建和調度過程如下:

1. 在主線程創建並放入管理列表.

2. 其他actor往本actor發送消息,消息進入msg_queue,本actor進入 global_queue等待調度。

3. 有工作線程處理完一堆任務了,向主線程詢問任務,主線程把本actor分配給這個工作線程。

4. 該工作線程取出msg,調用actor相應處理函數處理這個消息。

 

所以可見,actor數目與工作線程數目沒有必然的關系,當然理想狀態是,每個actor都有自己的處理線程,這里有消息來到時,就可以馬上處理,不用等待。

 

理論上,actor開的越多,業務邏輯就分的越細,每次處理的時間就越短,只要actor的數目超過線程數,就可以最大限度利用多核的優勢,cpu的調度就越充分。

所以actor模型設計關鍵在於如何將業務邏輯平攤到更多的actor上,而不是集中。 例如上面提到global_logic是多人玩法的業務邏輯,只要一細分,可以把分成

幫會actor,組隊actor,戰斗actor等等,這樣三個消息同時就有機會被三個cpu處理,而不是固定只有一個。

 

Actor可以理解成用戶級別的進程,與操作系統級別的進程分離,即使開很多Actor,只要工作線程數目設計合理( <= 系統cpu核數),就能保證線程能在一直同一個cpu上

進行操作,減少線程切換的消耗,這對於cpu核數小的機器非常有用。 而對於像24核的機器,因為開辟的線程數是配置的,所以也很好規划一台機器能部署多少個服。

而多進程如果要對某些功能進行擴展,如增加login,增加logic,就是要增加一個系統線程,一旦進程數超過了cpu核,就會有時間浪費在切換線程上了,

這是一個缺點。

而Actor模型本身是優秀的,但是Actor的調度算法有會有很多種實現,而且必然涉及到鎖的設計,這就需要設計者的設計功力了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM