你用过Python异步编程吗?贪吃蛇游戏,专为多人游戏而设计。游戏入口在此,点此体验。1.简介在技术和文化领域,大型多人在线游戏(MMO)无疑是当今世界的趋势之一。长期以来,为MMO游戏编写服务器总是涉及大量预算和复杂的底层编程技能,但近年来,情况发生了迅速变化。基于动态语言的现代框架允许在中档硬件上处理大量并发用户连接。同时,HTML5和WebSockets标准使创建基于实时图形的游戏成为可能,这些游戏无需任何扩展即可直接在浏览器客户端运行。Python可能不是创建可扩展的非阻塞服务器的最佳工具,尤其是与该领域最好的Node.js相比。但最新版本的Python正在改变这一点。asyncio和特殊的async/await语法的引入使得异步代码看起来像常规的阻塞代码,这使得Python成为值得信赖的异步编程语言,因此我将尝试使用这些新特性来创建多人在线游戏。2.异步游戏服务器应该能够接受尽可能多的用户并发连接并实时处理这些连接。典型的解决方案是创建线程,但这并不能解决本例中的问题。运行数千个线程需要CPU在它们之间不断切换(这称为上下文切换),这是非常昂贵且低效的。用进程来实现就更糟了,因为,不仅如此,它们还占用大量内存。在Python中,甚至还有一个问题,Python的解释器(CPython)并不是为多线程设计的,而是主要针对单线程应用,以达到最佳性能。这就是为什么它使用GIL(全局解释器锁),一种不允许Python代码的多个线程并发运行的架构,以防止不受控制地使用同一个共享对象。通常,解释器会在当前线程等待时切换到另一个线程,通常是等待I/O响应(例如等待来自Web服务器的响应)。这允许在您的应用程序中进行非阻塞I/O操作,因为每个操作只阻塞一个线程而不是阻塞整个服务器。然而,这也使得通常的多线程解决方案几乎毫无用处,因为它不允许您并发执行Python代码,即使在多核CPU上也是如此。同时,完全可以在单线程中进行非阻塞I/O,从而无需频繁切换上下文。事实上,您可以用纯Python代码实现单线程非阻塞I/O。您所需要的只是标准选择模块,它允许您编写一个事件循环来等待非阻塞套接字上的I/O。但是,这种方法要求您在一个地方定义应用程序的所有逻辑,不久之后,您的应用程序就会变成一个非常复杂的状态机。有一些框架可以简化这项任务,比较流行的是tornade和twisted。它们用于使用回调方法(这类似于Node.js)来实现复杂的协议。这个框架在它自己的事件循环中运行,在定义的事件上调用你的回调函数。而且,这可能是某些情况下的解决方案,但它仍然需要使用回调进行编程,这会使您的代码碎片化。比起写同步代码,并发执行多份副本,就跟我们在普通线程上做的一样。为什么这在单个线程上是不可能的?这就是微线程(microthreads)这个概念产生的原因。这个想法是在一个线程上并发执行任务。当您在任务中调用阻塞方法时,称为“管理器”(或“调度程序”)的东西会执行事件循环。当有一些事件准备好处理时,管理器将执行权转移给任务并等待它完成。任务将执行直到遇到阻塞调用,此时它将执行返回给管理器。微线程也称为轻量级线程或绿色线程(来自Java中的一个术语)。在伪线程中并发执行的任务称为tasklet、greenlet或协程。Python中tasklet的最早实现之一是StacklessPython。之所以如此出名,是因为它被用在了一款非常有名的网络游戏EVEonline中。这个MMO声称有成千上万的玩家在一个持久的“宇宙”中进行不同的活动,所有这些都是实时发生的。Stackless是一个独立的Python解释器,它取代了标准的函数堆栈调用,直接控制程序运行过程,减少上下文切换的开销。虽然这很好用,但这种解决方案不如在标准解释器中使用“软”库受欢迎,eventlet和gevent等包带有修补的标准I/O库,I/O函数将执行传递给内部事件循环。这使得将正常阻塞代码转换为非阻塞代码变得容易。这种方法的一个缺点是从代码上看并不明显,它的调用是非阻塞的。较新版本的Python引入了原生协程作为生成器的高级形式。Python3.4版本后引入了asyncio库,它依赖原生协程提供单线程并发。但只是在Python3.5之后,协程才成为Python语言的一部分,使用新的关键字async和await来描述。这是一个演示如何使用asyncio运行并发任务的简单示例。importasyncioasyncdefmy_task(seconds):print("startsleepingfor{}seconds".format(seconds))awaitasyncio.sleep(seconds)print("endsleepingfor{}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秒,输出如下:startssleepingfor1秒开始休眠2秒结束休眠1秒结束休眠2秒你看,协程不会互相阻塞——第二个任务在第一个任务完成之前开始。发生这种情况的原因是asyncio.sleep是一个协程,它将执行返回给调度程序,直到时间到了。在下一节中,我们将使用基于协程的任务来创建游戏循环。使用Python和Asyncio编写在线多人游戏(2)使用Python和Asyncio编写在线多人游戏(3)
