Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型
使用协程建立自己的异步非阻塞模型
接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助。主要参考为文末的链接,以及自己的补充理解。
完整代码
#!/usr/bin/python
# =============================================================
# File Name: async_base.py
# Author: LI Ke
# Created Time: 1/29/2018 09:18:50
# ============================================================= import types
import time @types.coroutine
def switch():
print('Switch: Start')
yield
print('Switch: Done') async def coro_1():
print('C1: Start')
await switch()
print('C1: Stop') async def coro_2():
print('C2: Start')
print('C2: 1')
print('C2: 2')
print('C2: 3')
print('C2: Stop') c_1 = coro_1()
c_2 = coro_2() try:
c_1.send(None)
except StopIteration:
pass
try:
c_2.send(None)
except StopIteration:
pass
try:
c_1.send(None)
except StopIteration:
pass print('--------------------------------') def run(coros):
coros = list(coros) while coros:
# Duplicate list for iteration so we can remove from original list
for coro in list(coros):
try:
coro.send(None)
except StopIteration:
coros.remove(coro) c_1 = coro_1()
c_2 = coro_2()
run([c_1, c_2]) print('--------------------------------') @types.coroutine
def action(t):
trace=[]
while True:
trace.append(time.time())
if trace[-1] - trace[0] > t:
break # This break will end this function and raise a StopIteration
yield async def coro_1():
print('C1: Start')
await action(2)
print('C1: Stop') async def coro_2():
print('C2: Start')
await action(3)
print('C2: Stop') def timeit(f):
def _wrapper(*args, **kwargs):
start = time.time()
re = f(*args, **kwargs)
end = time.time()
print('Time cost:', f.__name__, end-start)
return re
return _wrapper c_1 = coro_1()
c_2 = coro_2()
timeit(run)([c_1])
timeit(run)([c_2]) print('--------------------------------') c_1 = coro_1()
c_2 = coro_2()
timeit(run)([c_1, c_2])
分段解释
首先会导入需要的模块,这里仅仅使用types和time两个模块,放弃异步I/O的asyncio模块。
import types
import time
接下来定义一个switch函数,利用types.coroutine装饰器将switch装饰成一个协程,这个协程将完成一个切换功能。
@types.coroutine
def switch():
print('Switch: Start')
yield
print('Switch: Done')
随后定义第一个协程,协程启动后,会进入一个await,即切入刚才的switch协程,这里使用async和await关键字完成对协程的定义。
async def coro_1():
print('C1: Start')
await switch()
print('C1: Stop')
同样的,再定义第二个协程,第二个协程将从头到尾顺序执行。
async def coro_2():
print('C2: Start')
print('C2: 1')
print('C2: 2')
print('C2: 3')
print('C2: Stop')
有了上面的两个协程,但我们在异步时,希望在执行完C_1的start后,切换进协程C_2,执行完成后再切换回来。那么此时就需要一个对协程切换进行控制的程序,具体顺序如下,
- 启动协程c_1,启动后会切换进switch函数,
- Switch中由于yield而切出,并保留上下文环境
- c_1.send()将获得返回结果(如果有的话),并继续执行
- 此时c_1已经被中止,启动c_2,则完成所有执行步骤,捕获生成器的中止异常
- 这时c_2以执行完毕,再次切回c_1(此时会从switch yield之后开始执行)继续执行。
c_1 = coro_1()
c_2 = coro_2() try:
c_1.send(None)
except StopIteration:
pass
try:
c_2.send(None)
except StopIteration:
pass
try:
c_1.send(None)
except StopIteration:
pass
最终得到结果如下,可以看到,整个过程完全按期望的流程进行,
C1: Start
Switch: Start
C2: Start
C2: 1
C2: 2
C2: 3
C2: Stop
Switch: Done
C1: Stop
但是这里的协程运行部分仍需改善,于是接下来便定义一个run函数用于执行一个协程列表。
run函数首先会遍历协程列表的副本,并不断尝试启动列表中的协程,当协程结束后便将协程从协程列表中删除,直到所有的协程都执行完毕为止。
def run(coros):
coros = list(coros) while coros:
# Duplicate list for iteration so we can remove from original list
for coro in list(coros):
try:
coro.send(None)
except StopIteration:
coros.remove(coro) c_1 = coro_1()
c_2 = coro_2()
run([c_1, c_2])
测试一下run函数,得到结果与前面相同,
C1: Start
Switch: Start
C2: Start
C2: 1
C2: 2
C2: 3
C2: Stop
Switch: Done
C1: Stop
到目前为止,完成了一个简单的异步模型的搭建,即c_2无需等待c_1执行完成再继续执行,而是由c_1交出了控制权进行协作完成,同时也不存在多线程的抢占式任务,因为由始至终都只有一个线程在运行,而且也没有混乱的回调函数存在。
但是,还存在一个阻塞问题没有解决,也就是说,如果c_1中的switch函数是一个耗时的I/O操作或其他阻塞型操作,则此时需要等待switch的阻塞操作完成才能交出控制权,可如果希望在等待这个耗时操作时,先去执行c_2的任务,再回来检测c_1中的耗时操作是否完成,则需要使用非阻塞的方式。
首先,对刚才的switch进行改造,完成一个action协程,这个协程会根据传入的参数,执行对应时间后,再退出协程引发StopIteration,实现方式如下,每次切换进action中都会记录下时间,然后将时间和第一次进入的时间进行对比,如果超过了设置的时间便退出,如果没超过限制时间,则切出协程交还出控制权。
@types.coroutine
def action(t):
trace=[]
while True:
trace.append(time.time())
if trace[-1] - trace[0] > t:
break # This break will end this function and raise a StopIteration
yield
接着定义两个协程,分别执行action时间为2秒和3秒,同时定义一个计算时间的装饰器,用于时间记录。
async def coro_1():
print('C1: Start')
await action(2)
print('C1: Stop') async def coro_2():
print('C2: Start')
await action(3)
print('C2: Stop') def timeit(f):
def _wrapper(*args, **kwargs):
start = time.time()
re = f(*args, **kwargs)
end = time.time()
print('Time cost:', f.__name__, end-start)
return re
return _wrapper
然后我们先分别运行两个协程进行一个实验,
c_1 = coro_1()
c_2 = coro_2()
timeit(run)([c_1])
timeit(run)([c_2])
从输出的结果可以看到两个协程的耗时与action执行的时间基本相同,且顺序执行的时间为两者之和,
C1: Start
C1: Stop
Time cost: run 2.030202865600586
C2: Start
C2: Stop
Time cost: run 3.0653066635131836
接下来,利用异步非阻塞的方式来执行这两个协程,
c_1 = coro_1()
c_2 = coro_2()
timeit(run)([c_1, c_2])
最后得到结果
C1: Start
C2: Start
C1: Stop
C2: Stop
Time cost: run 3.0743072032928467
从结果中可以看到,此时的运行方式是异步的形式,c_1启动后由于进入一个耗时action,且action被我们设置为非阻塞形式,因此c_1交出了控制权,控制权回到run函数后,启动了c_2,而c_2同样也进入到action中,这时两个协程都在等待任务完成,而监视run则在两个协程中不停轮询,不断进入action中查看各自的action操作是否完成,当有协程完成后,将继续启动这个协程的后续操作,直到最终所有协程结束。
按照非阻塞异步协程的方式,可以以单线程运行,避免资源锁的建立,也消除了线程切换的开销,并且最终获得了类似多线程运行的时间性能。
相关阅读
参考链接
http://www.oschina.net/translate/playing-around-with-await-async-in-python-3-5
Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型的更多相关文章
- Python的异步编程[0] -> 协程[0] -> 协程和 async / await
协程 / Coroutine 目录 生产者消费者模型 从生成器到异步协程– async/await 协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时 ...
- Python的网络编程[0] -> socket[1] -> socket 模块
socket 1. 常量 / Constants AF_* 和 SOCK_* 分别属于 AddressFamily 和 SocketType 1.1 AF_*类常量 socket.AF_UNIX: ...
- 异步编程系列第02章 你有什么理由使用Async异步编程
p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...
- Python 之并发编程之进程上(基本概念、并行并发、cpu调度、阻塞 )
一: 进程的概念:(Process) 进程就是正在运行的程序,它是操作系统中,资源分配的最小单位. 资源分配:分配的是cpu和内存等物理资源 进程号是进程的唯一标识 同一个程序执行两次之后是两个进程 ...
- python IO非阻塞模型
server端 import socket sk = socket.socket() sk.bind(('127.0.0.1', 8010)) sk.setblocking(False) # sk.l ...
- Python的Web编程[0] -> Web客户端[1] -> Web 页面解析
Web页面解析 / Web page parsing 1 HTMLParser解析 下面介绍一种基本的Web页面HTML解析的方式,主要是利用Python自带的html.parser模块进行解析.其 ...
- Python的Web编程[0] -> Web客户端[0] -> 统一资源定位符 URL
统一资源定位符 / URL 目录 URL 构成 URL 解析 URL 处理 1 URL构成 统一资源定位符(Uniform Resource Locator) 是对可以从互联网上得到的资源的位置和访问 ...
- Python的网络编程[0] -> socket[0] -> socket 与 TCP / UDP
Socket socket 简述 / socket Abstract 网络进程通信与 socket 网络中进程之间如何通信,首要解决的问题是如何唯一标识一个进程,否则通信无从谈起.在本地可以通过进程 ...
- Python的网络编程[0] -> socket[2] -> 利用 socket 建立 TCP/UDP 通信
Socket 目录 socket 的 TCP/IP 通信基本建立过程 socket 的 UDP 通信基本建立过程 socket 的 UDP 广播式通信基本建立过程 socket 的多线程通信建立过程 ...
随机推荐
- 纯真IP数据库(qqwry.dat)转换成最新的IP数据库格式(ipwry.dat)
纯真IP数据库(qqwry.dat)转换成最新的IP数据库格式(ipwry.dat) 转载自:http://blog.cafeboy.org/2011/02/25/qqwry-to-ipwry/ ht ...
- 【题解】CQOI2015选数
这题做的时候接连想错了好多次……但是回到正轨上之后依然是一个套路题.(不过这题好像有比莫比乌斯反演更好的做法,莫比乌斯反演貌似是某种能过的暴力ヽ(´ー`)┌)不过能过也就行了吧哈哈. 首先我们把数字的 ...
- GYM - 100814 C.Connecting Graph
题意: 初始有n个点,m次操作.每次操作加一条边或者询问两个点第一次连通的时刻(若不连通输出-1). 题解: 用并查集维护每个点所在连通块的根.对于每次加边,暴力的更新新的根. 每次将2个块合并时,将 ...
- BZOJ1115 [POI2009]石子游戏Kam 【博弈论——阶梯游戏】
题目 有N堆石子,除了第一堆外,每堆石子个数都不少于前一堆的石子个数.两人轮流操作每次操作可以从一堆石子中移走任意多石子,但是要保证操作后仍然满足初始时的条件谁没有石子可移时输掉游戏.问先手是否必胜. ...
- [USACO] 2004 Open MooFest 奶牛集会
题目背景 MooFest, 2004 Open 题目描述 约翰的N 头奶牛每年都会参加"哞哞大会".哞哞大会是奶牛界的盛事.集会上的活动很 多,比如堆干草,跨栅栏,摸牛仔的屁股等等 ...
- POJ3259:Wormholes(spfa判负环)
Wormholes Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 68097 Accepted: 25374 题目链接: ...
- HDU 多校对抗赛 C Triangle Partition
Triangle Partition Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 132768/132768 K (Java/Oth ...
- There is an overlap in the region chain
ERROR: (regions day_hotstatic,860010-2355010000_20140417_12_entry_00000000321,1398674475358.0dc20573 ...
- JS中二维数组的声明
var myarr=new Array(); //先声明一维 for(var i=0;i<2;i++){ //一维长度为2 myarr[i]=new Array(); //再声明二维 for(v ...
- SQL性能分析
MySQL常见瓶颈: CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候. IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候. 服务器硬件的性能瓶颈:top.free.io ...