Python遊戲伺服器開發日記(五)skynet_messagequeue和skynet_timer
本文不想討論太細節的問題,具體的實現思路我並沒有理的非常清晰,還是看程式碼為好。我這裡從實際需求出發,寫一些tips,方便新來的小夥伴參考。
skynet的timer和mq,解耦做的非常好,寫一個test.c,將它們單獨編譯,即可單獨測試這兩個模組,呼叫標頭檔案裡提供的api,可以加深理解。我是直接把它們放到了我的工程裡:)
------------ skynet_timer ---------------
skynet的timer,是單獨啟動了一個執行緒,不斷的呼叫tick,檢查下一個需要被觸發的timer。
timer的結構體,包含一個near陣列和t[4]二維陣列,要注意他倆的長度也不一樣。。。處理的時候比較繁雜。
分成near和t兩個陣列,其實是為了優化。系統time是毫秒級的,每個node裡儲存的都是絕對時間,根據絕對時間離現在的遠近,放在不同的數組裡。
每次只需要查詢near裡的timer,比較快速。
判斷時間遠近的演算法都是借用位運算,符合雲風的風格。對C的二進位制操作不熟悉的朋友,可能會略難懂。
明白了以上幾點,如果看清了timer每個函式的意思,還是比較好理解的。剩下一個難點就是時間的迴繞問題。
迴繞的原因是用毫秒級計數器儲存時間,int32只能儲存幾十天的長度,計數器必然會在伺服器工作時從0xffffffff回到0,skynet_timer的處理比較奇葩,在add_node的時候,沒有做任何特殊處理,導致糊里糊塗的把node加入了佇列。
分析的關鍵一點就破——所有可能迴繞的node(時間不要過於太遠的),都被陰差陽錯的放在了t[3][0]裡面。
然後檢測到當前時間點回繞的時候,只要把t[3][0]裡的node全都拿出來重新插入一遍即可。這個方法比較trick,反正結果正確就行。
------------- skynet_mq -----------------
skynet的message_queue,是global_queue和service私有queue的結合體。
有了service私有queue,每個service同時就只能處理一個訊息!這是重點,這是天然的鎖,在整個架構中,其實這是一個重點。
它保證了同一個service不會出現併發。
還是強調一下上面這點,我設計自己的系統時老忘 :)
global_queue是掛了一串mq,service處理訊息時,把自己的mq取下來,處理完再掛回去。可以避免鎖的使用。
其實後來,global_queue的處理還是加上了鎖(貌似叫自旋鎖?),因為其實skynet較早的完全無鎖實現,並不能通過某些仔細設計的單元測試(這一點我是從github的上傳記錄裡看到的)。問了雲風本人,雲大表示至少現在的邏輯更清晰了。
------------ 理解context和session -----------
context 是service執行內容的上下文,貌似一個service只有一個context。
session是一個整數,用於管理coroutine。lua的coroutine本人非常不熟,這塊也無法借鑑。好在python的greenlet我認為更方便使用,greenlet的 C API 也很清晰,很容易和messagequeue嵌入到一起。
每個context或者說每個service,每當呼叫skynet.call或者skynet.sleep等導致掛起的函式時,都會產生一個coroutine,分配一個新的session號(簡單+1即可),等callback的時候,用這個session號找回原來的coroutine繼續執行。
(這塊留空,我還要再仔細確認一下,繼續完善)