Writing online multiplayer game with python and asyncio - part 1 (#4198)

* Create 20160524 Writing online multiplayer game with python and asyncio - part 1.md

* Delete 20160524 Writing online multiplayer game with python and asyncio - part 1.md
This commit is contained in:
Xin Wang 2016-07-20 09:48:45 +08:00 committed by Ezio
parent 94e3b69470
commit c0f397c051
2 changed files with 71 additions and 73 deletions

View File

@ -1,73 +0,0 @@
xinglianfly translate
Writing online multiplayer game with python and asyncio - part 1
===================================================================
Have you ever combined async with Python? Here Ill tell you how to do it and show it on a [working example][1] - a popular Snake game, designed for multiple players.
[Play gmae][2]
### 1. Introduction
Massive multiplayer online games are undoubtedly one of the main trends of our century, in both tech and cultural domains. And while for a long time writing a server for a MMO game was associated with massive budgets and complex low-level programming techniques, things are rapidly changing in the recent years. Modern frameworks based on dynamic languages allow handling thousands of parallel user connections on moderate hardware. At the same time, HTML 5 and WebSockets standards enabled the creation of real-time graphics-based game clients that run directly in web browser, without any extensions.
Python may be not the most popular tool for creating scalable non-blocking servers, especially comparing to node.js popularity in this area. But the latest versions of Python are aimed to change this. The introduction of [asyncio][3] library and a special [async/await][4] syntax makes asynchronous code look as straightforward as regular blocking code, which now makes Python a worthy choice for asynchronous programming. So I will try to utilize these new features to demonstrate a way to create an online multiplayer game.
### 2. Getting asynchronous
A game server should handle a maximum possible number of parallel users' connections and process them all in real time. And a typical solution - creating threads, doesn't solve a problem in this case. Running thousands of threads requires CPU to switch between them all the time (it is called context switching), which creates big overhead, making it very ineffective. Even worse with processes, because, in addition, they do occupy too much memory. In Python there is even one more problem - regular Python interpreter (CPython) is not designed to be multithreaded, it aims to achieve maximum performance for single-threaded apps instead. That's why it uses GIL (global interpreter lock), a mechanism which doesn't allow multiple threads to run Python code at the same time, to prevent uncontrolled usage of the same shared objects. Normally the interpreter switches to another thread when currently running thread is waiting for something, usually a response from I/O (like a response from web server for example). This allows having non-blocking I/O operations in your app, because every operation blocks only one thread instead of blocking the whole server. However, it also makes general multithreading idea nearly useless, because it doesn't allow you to execute python code in parallel, even on multi-core CPU. While at the same time it is completely possible to have non-blocking I/O in one single thread, thus eliminating the need of heavy context-switching.
Actually, a single-threaded non-blocking I/O is a thing you can do in pure python. All you need is a standard [select][5] module which allows you to write an event loop waiting for I/O from non-blocking sockets. However, this approach requires you to define all the app logic in one place, and soon your app becomes a very complex state-machine. There are frameworks that simplify this task, most popular are [tornado][6] and [twisted][7]. They are utilized to implement complex protocols using callback methods (and this is similar to node.js). The framework runs its own event loop invoking your callbacks on the defined events. And while this may be a way to go for some, it still requires programming in callback style, what makes your code fragmented. Compare this to just writing synchronous code and running multiple copies concurrently, like we would do with normal threads. Why wouldn't this be possible in one thread?
And this is where the concept of microthreads come in. The idea is to have concurrently running tasks in one thread. When you call a blocking function in one task, behind the scenes it calls a "manager" (or "scheduler") that runs an event loop. And when there is some event ready to process, a manager passes execution to a task waiting for it. That task will also run until it reaches a blocking call, and then it will return execution to a manager again.
>Microthreads are also called lightweight threads or green threads (a term which came from Java world). Tasks which are running concurrently in pseudo-threads are called tasklets, greenlets or coroutines.
One of the first implementations of microthreads in Python was [Stackless Python][8]. It got famous because it is used in a very successful online game [EVE online][9]. This MMO game boasts about a persistent universe, where thousands of players are involved in different activities, all happening in the real time. Stackless is a standalone Python interpreter which replaces standard function calling stack and controls the flow directly to allow minimum possible context-switching expenses. Though very effective, this solution remained less popular than "soft" libraries that work with a standard interpreter. Packages like [eventlet][10] and [gevent][11] come with patching of a standard I/O library in the way that I/O function pass execution to their internal event loop. This allows turning normal blocking code into non-blocking in a very simple way. The downside of this approach is that it is not obvious from the code, which calls are non-blocking. A newer version of Python introduced native coroutines as an advanced form of generators. Later in Python 3.4 they included asyncio library which relies on native coroutines to provide single-thread concurrency. But only in python 3.5 coroutines became an integral part of python language, described with the new keywords async and await. Here is a simple example, which illustrates using asyncio to run concurrent tasks:
```
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()
```
We launch two tasks, one sleeps for 1 second, the other - for 2 seconds. The output is:
```
start sleeping for 1 seconds
start sleeping for 2 seconds
end sleeping for 1 seconds
end sleeping for 2 seconds
```
As you can see, coroutines do not block each other - the second task starts before the first is finished. This is happening because asyncio.sleep is a coroutine which returns execution to a scheduler until the time will pass. In the next section, we will use coroutine-based tasks to create a game loop.
--------------------------------------------------------------------------------
via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/
作者:[Kyrylo Subbotin][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/
[1]: http://snakepit-game.com/
[2]: http://snakepit-game.com/
[3]: https://docs.python.org/3/library/asyncio.html
[4]: https://docs.python.org/3/whatsnew/3.5.html#whatsnew-pep-492
[5]: https://docs.python.org/2/library/select.html
[6]: http://www.tornadoweb.org/
[7]: http://twistedmatrix.com/
[8]: http://www.stackless.com/
[9]: http://www.eveonline.com/
[10]: http://eventlet.net/
[11]: http://www.gevent.org/

View File

@ -0,0 +1,71 @@
使用python 和asyncio编写在线多人游戏 - 第1部分
===================================================================
你曾经把async和python关联起来过吗在这里我将告诉你怎样做而且在[working example][1]这个例子里面展示-一个流行的贪吃蛇游戏,这是为多人游戏而设计的。
[Play game][2]
###1.简介
在技术和文化领域大量的多人在线游戏毋庸置疑是我们这个世界的主流之一。同时为一个MMO游戏写一个服务器一般和大量的预算与低水平的编程技术相关在最近这几年事情发生了很大的变化。基于动态语言的现代框架允许在稳健的硬件上面处理大量并发的用户连接。同时HTML5 和 WebSockets 标准允许基于实时的图形游戏直接在web浏览器上创建客户端而不需要任何的扩展。
对于创建可扩展非堵塞的服务器Python可能不是最受欢迎的工具尤其是和在这个领域最受欢迎的node.js相比。但是最近版本的python打算改变这种现状。[asyncio][3]的介绍和一个特别的[async/await][4] 语法使得异步代码看起来像常规的阻塞代码这使得python成为一个值得信赖的异步编程语言。所以我将尝试利用这些新特点来创建一个多人在线游戏。
###2.异步
一个游戏服务器应该处理最大数量的用户的并发连接和实时处理这些连接。一个典型的解决方案----创建线程然而在这种情况下并不能解决这个问题。运行上千的线程需要CPU在它们之间不停的切换这叫做上下文切换这将开销非常大效率很低下。更糟糕的是因为此外它们会占用大量的内存。在python中还有一个问题python的解释器CPython并不是针对多线程设计的它主要针对于单线程实现最大数量的行为。这就是为什么它使用GILglobal interpreter lock,一个不允许同时运行多线程python代码的架构来防止共享物体的不可控用法。正常情况下当当前线程正在等待的时候解释器转换到另一个线程通常是一个I/O的响应像一个服务器的响应一样。这允许在你的应用中有非阻塞I/O因为每一个操作仅仅堵塞一个线程而不是堵塞整个服务器。然而这也使得通常的多线程变得无用因为它不允许你并发执行python代码即使是在多核心的cpu上。同时在单线程中拥有非阻塞IO是完全有可能的因而消除了经常切换上下文的需要。
实际上你可以用纯python代码来实现一个单线程的非阻塞IO。你所需要的只是标准的[select][5]模块这个模块可以让你写一个事件循环来等待未阻塞的socket的io。然而这个方法需要你在一个地方定义所有app的逻辑不久之后你的app就会变成非常复杂的状态机。有一些框架可以简化这个任务比较流行的是[tornade][6] 和 [twisted][7]。他们被用来使用回调方法实现复杂的协议这和node.js比较相似。这个框架运行在他自己的事件循环中这个事件在定义的事件上调用你的回调。并且这或许是一些情况的解决方案但是它仍然需要使用回调的方式编程这使你的代码碎片化。和写同步代码并且并发执行多个副本相比就像我们会在普通的线程上做一样。这为什么在单个线程上是不可能的呢
这就是为什么microthread出现的原因。这个想法是为了在一个线程上并发执行任务。当你在一个任务中调用阻塞的方法时有一个叫做"manager" (或者“scheduler”)的东西在执行事件循环。当有一些事件准备处理的时候一个manager会让等这个事件的“任务”单元去执行直到自己停了下来。然后执行完之后就返回那个管理器manager
>Microthreads are also called lightweight threads or green threads (a term which came from Java world). Tasks which are running concurrently in pseudo-threads are called tasklets, greenlets or coroutines.Microthreads 也会被称为lightweight threads 或者 green threadsjava中的一个术语。在伪线程中并发执行的任务叫做taskletsgreenlets或者coroutines.
microthreads的其中一种实现在python中叫做[Stackless Python][8]。这个被用在了一个叫[EVE online][9]的非常有名的在线游戏中所以它变得非常有名。这个MMO游戏自称说在一个持久的宇宙中有上千个玩家在做不同的活动这些都是实时发生的。Stackless 是一个单独的python解释器它代替了标准的栈调用并且直接控制流来减少上下文切换的开销。尽管这非常有效这个解决方案不如使用标准解释器的“soft”库有名。像[eventlet][10]和[gevent][11] 的方式配备了标准的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是协同程序它会返回一个调度器的执行直到时间过去。在下一节中
我们将会使用coroutine-based的任务来创建一个游戏循环。
--------------------------------------------------------------------------------
via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/
作者:[Kyrylo Subbotin][a]
译者:[xinglianfly](https://github.com/xinglianfly)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/
[1]: http://snakepit-game.com/
[2]: http://snakepit-game.com/
[3]: https://docs.python.org/3/library/asyncio.html
[4]: https://docs.python.org/3/whatsnew/3.5.html#whatsnew-pep-492
[5]: https://docs.python.org/2/library/select.html
[6]: http://www.tornadoweb.org/
[7]: http://twistedmatrix.com/
[8]: http://www.stackless.com/
[9]: http://www.eveonline.com/
[10]: http://eventlet.net/
[11]: http://www.gevent.org/