原文地址 https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/
你試過使用python的async么?這里我來告訴你如何做,我們來寫出一個可以用的例子 -- 一個可以多人玩的貪吃蛇.
- 介紹.
MMO游戲毫無疑問是當(dāng)前主流趨勢,不管從技術(shù)角度還是從流行趨勢.曾幾何時,寫一個MMO游戲是需要大量的預(yù)算和非常復(fù)雜的底層的編程技術(shù).最近,事情發(fā)生變化了.很多基于動態(tài)語言的現(xiàn)在框架可以做到處理數(shù)以千計的玩家連接.(數(shù)以千計,咋一聽起來有點挫啊,可是仔細想想一個單獨的進程也差不多就是這個級別,大型的MMO都是在這個基礎(chǔ)上做多進程擴展).同時html 5和WebSockets標(biāo)準能夠?qū)崿F(xiàn)網(wǎng)頁運行的實時畫面游戲.
python可能不是創(chuàng)建可擴展的非阻塞服務(wù)器的最流行的工具,尤其和node.js對比.但是最新版本的python瞄準了這一問題.asyncio和async/await能夠是異步的語言看起來和常規(guī)的代碼一樣直接.所以我們來通過這些新特性來演示一下如何創(chuàng)建一個MMO游戲. - 變成異步
一個gameserver能夠處理大量玩家的并行連接,同時要做到實時.一個方法是創(chuàng)建線程,可是這種方法并不適用.運行幾千個線程,cpu就要不停的切換線程(context switching),這樣就非常抵消.如果用多進程呢?那更糟糕,因為需要占用更多的內(nèi)存.使用pyuthon還有一個更大的問題 - 通常python解釋器(CPython)設(shè)計上不能做到真正的多線程,它的目標(biāo)是單線程的效率. 這就是它使用GIL(global interpreter lock)的原因,這就使得python不同同時運行代碼,來防止共享對象的使用. 一般情況下,解釋器切換到另一個線程發(fā)生在當(dāng)前線程正咋等待io或者其他.這確實能夠做到費阻塞的io.因為只阻塞在一個線程里.然而,這并不能利用多線程的優(yōu)勢,因為不能夠同時運行代碼,即使是在多核cpu上面.其實呢, 在單線程里面也是完全可以做到非組賽io的,這樣就避免了大量的context-switching.
這種單線程非阻塞的實現(xiàn)通過純python就可以做到.你需要的是select.你自己要寫一個事件循環(huán).這種方法需要你把邏輯都寫在一個地方, 然而你的應(yīng)用會很快變成非常復(fù)雜的狀態(tài)機.有很多框架來簡化這個方法,最流行的是tornado和twisted.他們都是通過回調(diào)實現(xiàn)了非常復(fù)雜的協(xié)議.(有點像node.js)這些框架運行自己的事件循環(huán),在特定事件發(fā)生時調(diào)用你定義的回調(diào).雖然這樣已經(jīng)非常好了,但是這個風(fēng)格是callback,代碼會變得脆弱(不至于吧).和這個相對應(yīng)的是同步代碼.為什么不在一個線程里做到這些呢?
那么我就就要討論一個新概念, microthreads,(是叫微線程么).意思就是在單線程里面同步運行很多任務(wù).當(dāng)你調(diào)用一個阻塞的任務(wù),背后的"manager"會運行一個事件循環(huán).當(dāng)一個事件發(fā)生,這個manager就會通知等待這個事件的任務(wù).這個任務(wù)就繼續(xù)運行直到遇到一個阻塞,然后有把運行任務(wù)交給manager.
microthread 又叫做輕量級線程或者綠色線程.在這個偽線程中運行的任務(wù)就叫做tasklets,greenlets或者協(xié)程.
python中的microthread最開始的實現(xiàn)是叫做Stackless Python.為什么這個會出名呢?因為EVE online這個游戲.
這個MMO游戲是發(fā)生在一個大宇宙,里面有數(shù)以千計的玩家發(fā)生不同的活動,都是實時的. stackless是一個獨立的python解釋器,這個解釋器去掉了標(biāo)準的函數(shù)調(diào)用棧,直接控制流程,這樣contextswitch的代價最小.(沒看懂replaces standard function calling stack and controls the flow directly to allow minimum possible context-switching expenses. ). 雖然非常高效, 可是這個解釋器上能夠的lib就非常少. 我們看看eventlet和gevent,通過對標(biāo)準io進行修改,讓io把執(zhí)行權(quán)交給他們自己的內(nèi)部事件循環(huán).這樣就能夠把阻塞的代碼編程非阻塞的.這個方法的缺點是代碼上不太直觀.最新版的python介紹了一個原生的協(xié)程來作為生成器的高級形式.在python3.4, 引入了asyncio,依賴于原生協(xié)程,提供了單線程的同步.但是直到3.5才編程語言的一部分,使用了新的關(guān)鍵字async和await.下面是一個簡單的例子,解釋了asyncio運行同步任務(wù)的情況:
import asyncio
async def my_task(seconds):
print("start sleeping for {} seconds".format(seconds))
await asyncio.sleep(seconds)
print("end sleeping for {} seconds".format(seconds))
all_tasks = asyncio.gather(my_task(1), my_task(2))
loop = asyncio.get_event_loop()
loop.run_until_complete(all_tasks)
loop.close()
我們觸發(fā)了兩個任務(wù),一個睡一秒一個睡兩秒.輸出如下:
start sleeping for 1 seconds
start sleeping for 2 seconds
end sleeping for 1 seconds
end sleeping for 2 seconds
as u can c,兩個任務(wù)并沒有阻塞住,第二個任務(wù)在第一個任務(wù)結(jié)束之前就開始了. 這是因為asyncio.sleep是一個協(xié)程.下一次我們來看一下游戲的事件循環(huán).