Skip to content

Commit 40305c4

Browse files
committed
Merge pull request Voidly#6 from gorlf/master
Add Chapter 8
2 parents 4c444ba + 578e462 commit 40305c4

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

第八章/使用asyncio.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
我们可以定义asyncio是一个Python中驱动异步编程的模块。ayncio模块使用下列组合来实现异步编程:
2+
- Event loop: asyncio模块允许每个进程一个事件循环。
3+
- Coroutines(协程): asyncio的官方文档中指出,coroutine是遵循一定规则的发生器。它最吸引人的特点是在执行的时候能够暂停等待外部处理,当外部处理完成后又可以从原来的位置恢复执行。
4+
- Futures: Futures代表尚未完成的processing。
5+
- Tasks: 是asyncio.Future的子类,用于管理coroutines。
6+
7+
除了这些机制,asyncio还为应用开发提供了很多其他机制,比如传输和协议,可以使用TCP、SSL、UDP和管道进行通信。关于asyncio更多的信息请查看 https://docs.python.org/3.4/library/asyncio.html。
8+
9+
### 理解coroutines和rutures
10+
为了在asyncio中定义coroutine,我们使用@asyncio.coroutine装饰器。为了执行一个操作I/O或者其他可能阻塞循环事件的计算,我们必须使用`yield from`语法来暂停coroutine。但是暂停和恢复的机制怎样工作?Coroutine和asyncio.Future对象一起工作。我们可以总结操作如下:
11+
- Coroutine初始化,asyncio.Future在内部实例化或者作为参数传给coroutine。
12+
- 到达coroutine使用yield from的地方,coroutine暂停来等待yield from引发的计算。yield from实例等待yield from\<coroutine or asyncio.Future or asyncio Task\>构建。
13+
- 当yield from引发的计算结束,coroutine执行与coroutine关联的asyncio.Future对象的set_result(\<result\>)方法,通知事件循环coroutine可以被恢复。
14+
15+
16+
#### 使用coroutine和asyncio.Future
17+
下面是使用coroutine和asyncio.Future对象的一些例子:
18+
import asyncio
19+
@asyncio.coroutine
20+
def sleep_coroutine(f):
21+
yield from asyncio.sleep(2)
22+
f.set_result("Done!")
23+
在上述代码中,定义了一个协程sleep_coroutine,它接收一个asyncio.Future对象作为参数。在sleep_coroutine中,asyncio.sleep(2)会让协程睡眠2秒,asyncio.sleep已经和asyncio兼容。
24+
在主函数中创建asyncio.Future对象,创建event loop对象。
25+
if __name__ == '__main__':
26+
future = asyncio.Future()
27+
loop = asyncio.get_event_loop()
28+
loop.run_until_complete(sleep_coroutine(future))
29+
30+
> event loop执行的时候,任务和协程才会执行。
31+
32+
在最后一行,loop.run_until_complete(sleep_coroutine(future)),很明显就是运行直到sleep_coroutine结束。
33+
34+
#### 使用asyncio.Task
35+
asyncio.Task是asyncio.Future的子类,目的是管理协程。以下是一个例子,多个asyncio.Task将会在事件循环中被创建和分派。
36+
37+
import asyncio
38+
@asyncio.coroutine
39+
def sleep_coro(name, seconds=1):
40+
print("[%s] coroutine will sleep for %d second(s)…"
41+
% (name, seconds))
42+
yield yfrom asyncio.sleep(seconds)
43+
print("[%s] done!" % name)
44+
45+
sleep_coro协程会接收两个参数,name用来标识协程,seconds用来定义睡眠时间。
46+
47+
在主函数中,定义了一个包含三个asyncio.Task对象的列表:
48+
49+
if __name__ == '__main__':
50+
tasks = [asyncio.Task(sleep_coro('Task-A', 10)),
51+
asyncio.Task(sleep_coro('Task-B')),
52+
asyncio.Task(sleep_coro('Task-C'))]
53+
loop.run_until_complete(asyncio.gather(*tasks))
54+
55+
程序的运行结果如下:
56+
![](图片链接地址)
57+
58+
值得注意的是,程序的输出表明任务执行的顺序和申明的顺序一致,它们都不能阻塞event loop。
59+
60+
#### 使用和asyncio不兼容的库
61+
62+
asyncio是python新加入的模块,一些库还不能很好的兼容。我们重新实现之前章节的例子asyncio_task_sample.py,用time.sleep替换asyncio.sleep。运行结果如下:
63+
64+
![](图片链接地址)
65+

第八章/异步的做事.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
在之前的章节我们学习了用Celery框架分发任务,并且在同一个网络的不同机器上并行执行。现在我们将学习异步编程、事件循环和协程,它们都在Python3.4的asyncio模块占有重要的地位。
2+
本章将覆盖以下内容:
3+
4+
- 阻塞、非阻塞和异步操作
5+
- 理解事件循环
6+
- 使用异步IO(asyncio)
7+

第八章/总结.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
在本章节,我们学习了异步、阻塞、非阻塞编程。为了了解这些行为,我们使用asyncio模块的基本机制写了一些例子。
2+
3+
asyncio模块是对python异步编程进行革命的一个尝试。吉多范罗苏姆在探索性选择和提取基本机制为这些选择提供清晰的API方面非常成功。`yield from`语法产生是为了增强一些使用协程的程序的表现力,使程序员免去写回调函数的负担。除此之外,asyncio模块拥有与其它应用程序集成的能力。
4+
5+
快到本书的结束了,写这本书还是很有挑战性的,希望它对你有所帮助。本书中有很多东西没有介绍,比如 IPython, mpi4py, Greenlets, Eventlets, 等等。
6+
7+
基于本书提供的内容,你可以自己做实验比较不同的工具。几乎在本书的每一个章节都用了两个相关例子来介绍,这也说明了Python可以在不改变核心代码的基础上灵活的替换不同的工具。

第八章/理解事件循环.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
为了理解事件循环的概念,我们必须了解其内部结构。
2+
我们将用资源描述符代表套接口描述符和文件描述符。
3+
4+
### 轮询功能
5+
6+
不同的操作系统为了监控一个或多个资源描述符都实现了轮询功能。轮询方法是事件循环的基础,轮询方法会通知感兴趣的事件,资源描述符就会准备好做交互。然而,感兴趣的可能并不能完成理想的操作。
7+
8+
linux有下列轮询方法:
9+
10+
- select(): POSIX实现有以下几个缺点:
11+
- 监控资源描述符的数量有限
12+
- O(n)时间复杂度,n代表连接的客户端数
13+
- poll(): select()的增强版,有以下特点:
14+
- 允许监控更大范围的资源描述符
15+
- O(n)时间复杂度
16+
- 支持更多类型的监控事件
17+
- 和select()对比,可以复用entry数据
18+
- epoll(): Linux非常强大的方法,拥有O(1)时间复杂度。epoll()方法通过[epoll_wait](http://refspecs.linux-foundation.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/libc-epoll-wait-1.html)()提供两种监控事件。为了定义两种行为,我们假想一个场景,其中生产者往socket写数据,消费者等待数据。
19+
- 水平触发:当消费者调用epoll_wait(),它将得到资源描述符的状态并且立即返回,表明触发或者不触发读操作。水平触发和事件的状态有关而不是事件本身。
20+
- 边缘触发:epoll_wait()调用只有当写事件完成之后才会返回。所以边缘触发和事件有关。
21+
22+
23+
轮询方法工作的步骤如下:
24+
1. 创建poller对象
25+
2. poller中注册或者不注册1个或多个资源描述符
26+
3. 轮询方法在poller对象中执行
27+
28+
> Poller是一个提供使用轮询方法的抽象接口
29+
30+
### 使用事件循环
31+
32+
我们可以定义事件循环来简化使用轮询方法来监控事件。事件循环利用poller对象,使得程序员不用控制任务的添加、删除和事件的控制。
33+
事件循环使用回调方法来知道事件的发生。例如,有一个资源描述符A,当一个写事件在A中发生就会调用一个回调函数。一些实现了事件循环的应用如下:
34+
- Tornado web server ( http://www.tornadoweb.org/en/stable/ )
35+
- Twisted ( https://twistedmatrix.com/trac/ )
36+
- asyncio ( https://docs.python.org/3.4/library/asyncio.html )
37+
- Gevent ( http://www.gevent.org/ )
38+
- Eventlet ( https://pypi.python.org/pypi/eventlet )
39+
40+
41+
42+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
理解任务执行的不同方式对于构建一个可伸缩的解决方案非常重要。正确的运用异步、阻塞和非阻塞操作能大大改善系统的响应时间。
2+
3+
### 理解阻塞操作
4+
5+
可以用银行职员服务客户的例子来看阻塞操作。当轮到客户的号码时,银行职员就只为该客户服务直到服务完成。银行职员不能同时为多个客户办理业务。当只有2名银行职员但是每小时来100名顾客时,进度就会很缓慢。这个例子就描述了阻塞操作,当一个任务要等待另一个任务结束时,阻塞其对资源的访问。
6+
7+
### 理解非阻塞操作
8+
9+
非阻塞操作和异步操作很容易混淆,它们是不同的概念。用一个现实的场景来说明,比如你去银行咨询一个业务,银行职员说现在还没有结果,你稍后再来或者过几天再来。这就是非阻塞操作。
10+
11+
> 非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,之后再retry。
12+
13+
14+
### 理解异步操作
15+
16+
假设有两个银行职员,没个职员有10个助理。这时,如果来了一个客户,他需要办理的业务耗时比较长,那么就请一个助理到后台单独为该客户服务。这样就不会阻塞其他客户。
17+
18+
> 注册一个回调函数,当条件满足时会触发该函数。

0 commit comments

Comments
 (0)