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