Python 和 Asyncio 編寫在線多人遊戲(一)

 在技術和文化領域,大規模多人在線遊戲(MMO)毋庸置疑是咱們當今世界的潮流之一。很長時間以來,寫一個 MMO 遊戲這件事老是會涉及到大量的預算與複雜的底層編程技術。不過在最近這幾年,事情迅速發生了變化。基於動態語言的現代框架容許在中檔的硬件上面處理大量併發的用戶鏈接。同時,HTML5 和 WebSockets 標準使得實時圖形的遊戲能夠直接運行在瀏覽器的客戶端上,而不須要任何的擴展。
html

 

python

對於建立可擴展的非堵塞性的服務器來講,Python 可能不是最受歡迎的工具,尤爲是和在這個領域裏最受歡迎的 Node.js 相比而言。可是最近版本的 Python 正在改變這種現狀。asyncio 的引入和一個特別的 async/await 語法使得異步代碼看起來像常規的阻塞代碼同樣,這使得 Python 成爲了一個值得信賴的異步編程語言,因此本文將嘗試利用這些新特色來建立一個多人在線遊戲。python

異步
線程

一個遊戲服務器應該能夠接受盡量多的用戶併發鏈接,並實時處理這些鏈接。一個典型的解決方案是建立線程,然而在這種狀況下並不能解決這個問題。運行上千的線程須要 CPU 在它們之間不停的切換(這叫作上下文切換),這將致使開銷很是大,效率很低下。更糟糕的是使用進程來實現,由於它們還會佔用大量的內存。在 Python 中,甚至還有一個問題,Python 的解釋器(CPython)並非針對多線程設計的,相反它主要針對於單線程應用實現最大的性能。這就是爲何它使用 GIL(global interpreter lock),這是一個不容許同時運行多線程 Python 代碼的架構,以防止同一個共享對象出現使用不可控。正常狀況下,在當前線程正在等待的時候,解釋器會轉換到另外一個線程,一般是等待一個 I/O 的響應(好比等待 Web 服務器的響應)。這容許了在你的應用中實現非阻塞 I/O 操做,由於每個操做僅僅阻塞了一個線程而不是阻塞整個服務器。然而,這也使得一般的多線程方案變得幾近無用,由於它不容許你併發執行 Python 代碼,即便是在多核心的 CPU 上也是這樣。與此同時,在一個單一線程中擁有非阻塞 I/O 是徹底有可能的,於是消除了常常切換上下文的須要。linux

實際上,你能夠用純 Python 代碼來實現一個單線程的非阻塞 I/O。你所須要的只是標準的 select 模塊,這個模塊可讓你寫一個事件循環來等待未阻塞的 socket 的 I/O。然而,這個方法須要你在一個地方定義全部 app 的邏輯,用不了多久,你的 app 就會變成很是複雜的狀態機。有一些框架能夠簡化這個任務,比較流行的是 tornade 和 twisted 。它們被用來使用回調方法實現複雜的協議(這和 Node.js 比較類似)。這種框架運行在它本身的事件循環中,按照定義的事件調用你的回調函數。這或許是一些狀況的解決方案,可是它仍然須要使用回調的方式編程,這使你的代碼變得碎片化。與寫同步代碼而且併發地執行多個副本相比,這就像咱們在普通的線程上作的同樣。在單個線程上這爲何是不可能的呢?編程

微線程

上述就是爲何出現微線程(microthread)概念的緣由。這個想法是爲了在一個線程上併發執行任務。當你在一個任務中調用阻塞的方法時,有一個叫作「manager」 (或者「scheduler」)的東西在執行事件循環。當有一些事件準備處理的時候,一個 manager 會轉移執行權給一個任務,並等着它執行完畢。任務將一直執行,直到它遇到一個阻塞調用,而後它就會將執行權返還給 manager。
注:微線程也稱爲輕量級線程(lightweight threads)或綠色線程(green threads)(來自於 Java 中的一個術語)。在僞線程中併發執行的任務叫作 tasklets、greenlets 或者協程(coroutines)。瀏覽器

Python 中的微線程最先的實現之一是 Stackless Python 。它之因此這麼知名是由於它被用在了一個叫 EVE online 的很是有名的在線遊戲中。這個 MMO 遊戲自稱說在一個持久的「宇宙」中,有上千個玩家在作不一樣的活動,這些都是實時發生的。Stackless 是一個獨立的 Python 解釋器,它代替了標準的函數棧調用,而且直接控制程序運行流程來減小上下文切換的開銷。儘管這很是有效,這個解決方案不如在標準解釋器中使用「軟」庫更流行,像 eventlet 和 gevent 的軟件包配備了修補過的標準 I/O 庫,I/O 函數會將執行權傳遞到內部事件循環。這使得將正常的阻塞代碼轉變成非阻塞的代碼變得簡單。這種方法的一個缺點是從代碼上看這並不分明,它的調用是非阻塞的。新版本的 Python 引入了本地協程做爲生成器的高級形式。在 Python 的 3.4 版本以後,引入了 asyncio 庫,這個庫依賴於本地協程來提供單線程併發。可是僅僅到了 Python 3.5 ,協程就變成了 Python 語言的一部分,使用新的關鍵字 async 和 await 來描述。這是一個簡單的例子,演示了使用 asyncio 來運行併發任務。服務器

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()    

咱們啓動了兩個任務,一個睡眠 1 秒鐘,另外一個睡眠 2 秒鐘,輸出以下:多線程

start sleeping for 1 seconds
start sleeping for 2 seconds
end sleeping for 1 seconds
end sleeping for 2 seconds

正如你所看到的,協程不會阻塞彼此——第二個任務在第一個結束以前啓動。這發生的緣由是 asyncio.sleep 是協程,它會返回執行權給調度器,直到時間到了。架構

在下一節中,咱們將會使用基於協程的任務來建立一個遊戲循環。併發

本文轉載地址:https://www.linuxprobe.com/pyasyncio-mmo-game01.htmlapp

相關文章
相關標籤/搜索