官术网_书友最值得收藏!

2.9 使用Skynet的注意事項

使用一套引擎,就要理解它的特性。Skynet最大的特性是“提供同一機器上充分利用多核CPU的處理能力”,由此帶來的時序問題值得特別注意。

2.9.1 協程的作用

Skynet服務在收到消息時,會創建一個協程,在協程中會運行消息處理方法(即用skynet.dispatch設置的回調方法)。這意味著,如果在消息處理方法中調用阻塞API(如skynet.call、skynet.sleep、socket.read),服務不會被卡?。▋H僅是處理消息的協程被卡住),執行效率得以提高,但程序的執行時序將得不到保證。

如圖2-36所示,某個服務的消息隊列存在多條消息,第一條消息的處理函數是OnMsg1,第二條是OnMsg2,OnMsg1調用了阻塞方法skynet.sleep。盡管程序會依次調用OnMsg1、OnMsg2……但當執行到阻塞函數時,協程會掛起。實際執行順序可能是圖2-36中右邊展示的“語句1、skynet.sleep、語句3、語句4、語句2”。

圖2-36 使用阻塞API需要注意時序問題

2.9.2 扣除金幣的Bug

本節展示一種不注意時序問題導致的Bug,假設游戲有“存款”功能,玩家可以把一定數量的金幣存入銀行,獲得利息。相關服務如圖2-37所示,agent服務代表玩家控制的角色,bank服務代表銀行。存款的過程如下:客戶端發起存款請求(階段①),agent向bank轉達請求(階段②),bank會返回操作的結果(階段③)。代碼2-16展示了一種有Bug的寫法。

圖2-37 代碼2-16的示意圖

代碼2-16 agent的消息處理方法(有Bug)


local coin = 20 --角色身上的金幣數

function CMD.deposit(source)
    if coin < 20 then   --假設每次存20金幣
        return
    end
    local isok = skynet.call(bank, "lua", "deposit",  20);
    if isok then
        coin = coin - 20
    end
end

存在這么一種可能,玩家快速地兩次點擊存款按鈕,消息時序會按圖2-37中①①②③的順序執行。如果角色身上僅剩20金幣,第一次操作時,尚剩余20金幣,第二次操作時,依然剩余20金幣,兩次操作都能成功,玩家總共存入40金幣,剩余“-20”金幣,顯然不合理。

因為角色身上只有20金幣,正常的情況是,無論玩家多么快速地點擊存款按鈕,他都只能成功存入一次。代碼2-17展示了一種解決方法,在阻塞方法skynet.call之前扣除金幣,如果存款失敗,才補上扣除的金幣。

代碼2-17 修復代碼2-16的程序


function CMD.deposit(source)
    if coin < 20 then   --假設每次存20金幣
        return
    end

    coin = coin - 20
    local isok = skynet.call(bank, "lua", "deposit",  20);
    if not isok then
        coin = coin + 20
    end
end

現在,你已掌握了Skynet的特性和基本操作,接下來的一章,會用綜合示例說明怎樣用Skynet去開發真正的游戲項目。這里先打個預防針,下一章的難度頗高,代碼多、流程復雜,但如能掌握,你就擁有勝任“服務端開發工程師”崗位的條件。

主站蜘蛛池模板: 西畴县| 额敏县| 敖汉旗| 苏州市| 四平市| 桦南县| 乌拉特中旗| 兴业县| 郓城县| 孙吴县| 贡山| 陕西省| 灵武市| 合江县| 湘潭市| 浦东新区| 盘锦市| 平原县| 永清县| 西安市| 富裕县| 塔城市| 扎兰屯市| 米林县| 泰州市| 岑巩县| 乐平市| 隆子县| 家居| 苍南县| 会理县| 刚察县| 登封市| 蒙山县| 贵德县| 靖远县| 康保县| 抚宁县| 澎湖县| 山丹县| 台山市|