python实现异步非阻塞_Python的异步编程[0] -> 协程[1] -> 使用协程建立自己的异步非阻塞模型...

使用协程建立自己的异步非阻塞模型

接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助。主要参考为文末的链接,以及自己的补充理解。

完整代码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 #!/usr/bin/python

2 #=============================================================

3 #File Name: async_base.py

4 #Author: LI Ke

5 #Created Time: 1/29/2018 09:18:50

6 #=============================================================

7

8

9 importtypes10 importtime11

12

13 @types.coroutine14 defswitch():15 print('Switch: Start')16 yield

17 print('Switch: Done')18

19 async defcoro_1():20 print('C1: Start')21 await switch()22 print('C1: Stop')23

24

25 async defcoro_2():26 print('C2: Start')27 print('C2: 1')28 print('C2: 2')29 print('C2: 3')30 print('C2: Stop')31

32 c_1 =coro_1()33 c_2 =coro_2()34

35 try:36 c_1.send(None)37 exceptStopIteration:38 pass

39 try:40 c_2.send(None)41 exceptStopIteration:42 pass

43 try:44 c_1.send(None)45 exceptStopIteration:46 pass

47

48 print('--------------------------------')49

50 defrun(coros):51 coros =list(coros)52

53 whilecoros:54 #Duplicate list for iteration so we can remove from original list

55 for coro inlist(coros):56 try:57 coro.send(None)58 exceptStopIteration:59 coros.remove(coro)60

61 c_1 =coro_1()62 c_2 =coro_2()63 run([c_1, c_2])64

65 print('--------------------------------')66

67 @types.coroutine68 defaction(t):69 trace=[]70 whileTrue:71 trace.append(time.time())72 if trace[-1] - trace[0] >t:73 break #This break will end this function and raise a StopIteration

74 yield

75

76 async defcoro_1():77 print('C1: Start')78 await action(2)79 print('C1: Stop')80

81

82 async defcoro_2():83 print('C2: Start')84 await action(3)85 print('C2: Stop')86

87 deftimeit(f):88 def _wrapper(*args, **kwargs):89 start =time.time()90 re = f(*args, **kwargs)91 end =time.time()92 print('Time cost:', f.__name__, end-start)93 returnre94 return_wrapper95

96 c_1 =coro_1()97 c_2 =coro_2()98 timeit(run)([c_1])99 timeit(run)([c_2])100

101 print('--------------------------------')102

103 c_1 =coro_1()104 c_2 =coro_2()105 timeit(run)([c_1, c_2])

View Code

分段解释

首先会导入需要的模块,这里仅仅使用types和time两个模块,放弃异步I/O的asyncio模块。

1 importtypes2 import time

接下来定义一个switch函数,利用types.coroutine装饰器将switch装饰成一个协程,这个协程将完成一个切换功能。

1 @types.coroutine2 defswitch():3 print('Switch: Start')4 yield

5 print('Switch: Done')

随后定义第一个协程,协程启动后,会进入一个await,即切入刚才的switch协程,这里使用async和await关键字完成对协程的定义。

1 async defcoro_1():2 print('C1: Start')3 await switch()4 print('C1: Stop')

同样的,再定义第二个协程,第二个协程将从头到尾顺序执行。

1 async defcoro_2():2 print('C2: Start')3 print('C2: 1')4 print('C2: 2')5 print('C2: 3')6 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之后开始执行)继续执行。

1 c_1 =coro_1()2 c_2 =coro_2()3

4 try:5 c_1.send(None)6 exceptStopIteration:7 pass

8 try:9 c_2.send(None)10 exceptStopIteration:11 pass

12 try:13 c_1.send(None)14 exceptStopIteration:15 pass

最终得到结果如下,可以看到,整个过程完全按期望的流程进行,

C1: Start

Switch: Start

C2: Start

C2:1C2:2C2:3C2: Stop

Switch: Done

C1: Stop

但是这里的协程运行部分仍需改善,于是接下来便定义一个run函数用于执行一个协程列表。

run函数首先会遍历协程列表的副本,并不断尝试启动列表中的协程,当协程结束后便将协程从协程列表中删除,直到所有的协程都执行完毕为止。

1 defrun(coros):2 coros =list(coros)3

4 whilecoros:5 #Duplicate list for iteration so we can remove from original list

6 for coro inlist(coros):7 try:8 coro.send(None)9 exceptStopIteration:10 coros.remove(coro)11

12 c_1 =coro_1()13 c_2 =coro_2()14 run([c_1, c_2])

测试一下run函数,得到结果与前面相同,

C1: Start

Switch: Start

C2: Start

C2:1C2:2C2:3C2: 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中都会记录下时间,然后将时间和第一次进入的时间进行对比,如果超过了设置的时间便退出,如果没超过限制时间,则切出协程交还出控制权。

1 @types.coroutine2 defaction(t):3 trace=[]4 whileTrue:5 trace.append(time.time())6 if trace[-1] - trace[0] >t:7 break #This break will end this function and raise a StopIteration

8 yield

接着定义两个协程,分别执行action时间为2秒和3秒,同时定义一个计算时间的装饰器,用于时间记录。

1 async defcoro_1():2 print('C1: Start')3 await action(2)4 print('C1: Stop')5

6

7 async defcoro_2():8 print('C2: Start')9 await action(3)10 print('C2: Stop')11

12 deftimeit(f):13 def _wrapper(*args, **kwargs):14 start =time.time()15 re = f(*args, **kwargs)16 end =time.time()17 print('Time cost:', f.__name__, end-start)18 returnre19 return _wrapper

然后我们先分别运行两个协程进行一个实验,

1 c_1 =coro_1()2 c_2 =coro_2()3 timeit(run)([c_1])4 timeit(run)([c_2])

从输出的结果可以看到两个协程的耗时与action执行的时间基本相同,且顺序执行的时间为两者之和,

C1: Start

C1: Stop

Time cost: run2.030202865600586C2: Start

C2: Stop

Time cost: run3.0653066635131836

接下来,利用异步非阻塞的方式来执行这两个协程,

1 c_1 =coro_1()2 c_2 =coro_2()3 timeit(run)([c_1, c_2])

最后得到结果

C1: Start

C2: Start

C1: Stop

C2: Stop

Time cost: run3.0743072032928467

从结果中可以看到,此时的运行方式是异步的形式,c_1启动后由于进入一个耗时action,且action被我们设置为非阻塞形式,因此c_1交出了控制权,控制权回到run函数后,启动了c_2,而c_2同样也进入到action中,这时两个协程都在等待任务完成,而监视run则在两个协程中不停轮询,不断进入action中查看各自的action操作是否完成,当有协程完成后,将继续启动这个协程的后续操作,直到最终所有协程结束。

按照非阻塞异步协程的方式,可以以单线程运行,避免资源锁的建立,也消除了线程切换的开销,并且最终获得了类似多线程运行的时间性能。

相关阅读

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值