Python 和 Asyncio 编写在线多人游戏(一)
在技术和文化领域,大规模多人在线游戏(MMO)毋庸置疑是我们当今世界的潮流之一。很长时间以来,写一个 MMO 游戏这件事总是会涉及到大量的预算与复杂的底层编程技术。不过在最近这几年,事情迅速发生了变化。基于动态语言的现代框架允许在中档的硬件上面处理大量并发的用户连接。同时,HTML5 和 WebSockets 标准使得实时图形的游戏可以直接运行在浏览器的客户端上,而不需要任何的扩展。

对于创建可扩展的非堵塞性的服务器来说,Python 可能不是最受欢迎的工具,尤其是和在这个领域里最受欢迎的 Node.js 相比而言。但是最近版本的 Python 正在改变这种现状。asyncio 的引入和一个特别的 async/await 语法使得异步代码看起来像常规的阻塞代码一样,这使得 Python 成为了一个值得信赖的异步编程语言,所以本文将尝试利用这些新特点来创建一个多人在线游戏。
一个游戏服务器应该可以接受尽可能多的用户并发连接,并实时处理这些连接。一个典型的解决方案是创建线程,然而在这种情况下并不能解决这个问题。运行上千的线程需要 CPU 在它们之间不停的切换(这叫做上下文切换),这将导致开销非常大,效率很低下。更糟糕的是使用进程来实现,因为它们还会占用大量的内存。在 Python 中,甚至还有一个问题,Python 的解释器(CPython)并不是针对多线程设计的,相反它主要针对于单线程应用实现最大的性能。这就是为什么它使用 GIL(global interpreter lock),这是一个不允许同时运行多线程 Python 代码的架构,以防止同一个共享对象出现使用不可控。正常情况下,在当前线程正在等待的时候,解释器会转换到另一个线程,通常是等待一个 I/O 的响应(比如等待 Web 服务器的响应)。这允许了在你的应用中实现非阻塞 I/O 操作,因为每一个操作仅仅阻塞了一个线程而不是阻塞整个服务器。然而,这也使得通常的多线程方案变得几近无用,因为它不允许你并发执行 Python 代码,即使是在多核心的 CPU 上也是这样。与此同时,在一个单一线程中拥有非阻塞 I/O 是完全有可能的,因而消除了经常切换上下文的需要。
实际上,你可以用纯 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.html
Python 和 Asyncio 编写在线多人游戏(一)的更多相关文章
- 我实在不懂Python的Asyncio
原语 事件循环(Event Loop) Awaitables和Coroutines Coroutine Wrappers Awaitables and Futures Tasks Handles Ex ...
- 用 Python+nginx+django 打造在线家庭影院
用 Python+nginx+django 打造在线家庭影院 2018年11月29日 08:46:59 清如許 阅读数:1528 我喜欢看电影,尤其是好的电影,我会看上三四遍,仔细感受电影带给我的 ...
- Python网络编程——编写一个简单的回显客户端/服务器应用
今天将python中socket模块的基本API学习完后,照着书上的实例编写一个套接字服务器和客户端.采用python3.5版本,在注释中会标明python2和python3的不同之处. 1.代码 ( ...
- 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍,样例程序与本地调试
相关随笔: Hadoop-1.0.4集群搭建笔记 用python + hadoop streaming 编写分布式程序(二) -- 在集群上运行与监控 用python + hadoop streami ...
- 用python + hadoop streaming 编写分布式程序(二) -- 在集群上运行与监控
写在前面 相关随笔: Hadoop-1.0.4集群搭建笔记 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍,样例程序与本地调试 用python + hado ...
- 用python + hadoop streaming 编写分布式程序(三) -- 自定义功能
又是期末又是实训TA的事耽搁了好久……先把写好的放上博客吧 相关随笔: Hadoop-1.0.4集群搭建笔记 用python + hadoop streaming 编写分布式程序(一) -- 原理介绍 ...
- Linux 下Python调用C++编写的动态库
在工程中用到使用Python调用C++编写的动态库,结果报如下错误: OSError: ./extract_str.so: undefined symbol: _ZNSt8ios_base4InitD ...
- python练习:编写一个程序,要求用户输入10个整数,然后输出其中最大的奇数,如果用户没有输入奇数,则输出一个消息进行说明。
python练习:编写一个程序,要求用户输入10个整数,然后输出其中最大的奇数,如果用户没有输入奇数,则输出一个消息进行说明. 重难点:通过input函数输入的行消息为字符串格式,必须转换为整型,否则 ...
- python练习:编写一个程序,检查3个变量x,y,z,输出其中最大的奇数。如果其中没有奇数,就输出一个消息进行说明。
python练习:编写一个程序,检查3个变量x,y,z,输出其中最大的奇数.如果其中没有奇数,就输出一个消息进行说明. 笔者是只使用条件语句实行的.(if-else) 重难点:先把三个数进行由小到大的 ...
随机推荐
- FCKeditor如何升级CKEditor及使用方法
之前编辑器用的是FCKeditor,因为项目原因需要升级为最新版本4.2.2,发现是已经更名为CKEditor. 百度了一下,据官方的解释,CK是对FCK的代码的完全重写. 项目环境是asp.net的 ...
- ARM JTAG 信号 RTCK 应该如何处理?
用户在调试内嵌可综合内核的 CPU 如 ARM7TDMI-S 时,需要通过打开仿真器的自适应时钟功能. 此时,ARM仿真器根据 RTCK 时钟信号的频率,产生可用于 CPU 内核当前时钟主频的最快的 ...
- 【Go命令教程】1. 标准命令详解
Go 语言的 1.5 版本在标准命令方面有了重大变更.这倒不是说它们的用法有多大的变化,而是说它们的底层支持已经大变样了.让我们先来对比一下 $GOROOT/pkg/tool/< 平台相关目录 ...
- 网站性能工具-YSlow的23个规则-网站性能优化
1. 减少HTTP请求次数 合并图片.CSS.JS,改进首次访问用户等待时间. 2. 使用CDN 就近缓存==>智能路由==>负载均衡==>WSA全站动态加速 3. 避免空的src和 ...
- [web.config]如何灵活使用配置文件
摘要 在实际项目中,经常遇到比较多的环境,比如开发环境,测试环境,生产环境.对于这些环境,可能会有不同接口调用,不同的数据库连接字符串等等.那么该如何实现不同环境的参数快速切换呢?当然,最笨的方式就是 ...
- chrome主页被hao123篡改,怎么改回来?
这两天因为下载个别小程序又把我的chrome的主页给篡改了,由于我现在使用的是任务栏快捷方式,没法右键属性,但我想应该和桌面快捷方式是一个道理,于是我找到任务栏文件夹C:\Users\Administ ...
- 前端使用AngularJS的$resource,后端ASP.NET Web API,实现分页、过滤
在上一篇中实现了增删改查,本篇实现分页和过滤. 本系列包括: 1.前端使用AngularJS的$resource,后端ASP.NET Web API,实现增删改查2.前端使用AngularJS的$re ...
- log4j deadlock
用了这么久的Log4j这次倒下了,而且官方也还没有给出解决方案. 描述:tomcat 经过一天多时间的访问,出现了hang ,使用 Jstack 查看堆栈后,发现现成 blocked ,主要是 Log ...
- sqlite3 插入数据的时候,返回SQLITE_CONSTRAINT
sqlite3 插入数据的时候.返回SQLITE_CONSTRAINT 原因是:数据库的表的名字是纯数字. 大改这个原因太诡异了.创建的时候能够创建成功. 插入数据的时候就失败,由于表名是纯数字. 附 ...
- 数据访问:Implementing Efficient Transactions
An OLTP scenario is characterized by a large number of concurrent operations that create, update, an ...