流畅python学习笔记第十八章:使用asyncio包处理并发(二)
前面介绍了asyncio的用法。下面我们来看下如何用协程的方式来实现之前的旋转指针的方法
@asyncio.coroutine
def spin(msg):
write,flush=sys.stdout.write,sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status=char+''+msg
write(status)
flush()
write('\x08'*len(status))
try:
yield from asyncio.sleep(0.1)
except asyncio.CancelledError:
break
write(''*len(status)+'\0x8'*len(status))
@asyncio.coroutine
def slow_function():
yield from asyncio.sleep(3)
return 42
@asyncio.coroutine
def supervisor():
spinner=asyncio.ensure_future(spin('thinking'))
# spinner=asyncio.async(spin('thinking'))
print('spinner object:',spinner)
result=yield from slow_function()
spinner.cancel()
return result
if __name__=="__main__":
start=time.time()
loop=asyncio.get_event_loop()
result=loop.run_until_complete(supervisor())
loop.close()
print('Answer:',result)
end=time.time()
print("Total time:{}".format(end-start))
运行的结果和之前用多线程是一样的。我们来看下运行的具体原理
1 asyncio.get_event_loop和 loop.run_until_complete(supervisor())
创建主循环,并传入任务supervisor
2 在supervisor中首先通过asyncio.async(spin('thinking'))将spin函数添加如Task。方法也可以是通过spinner=asyncio.ensure_future(spin('thinking'))。在asyncio中Task对象和threading.Thread的用途是一样的。Task对象用于驱动协程,Thread对象用于调用可调用的对象,Task对象不用自己实例化,而是通过把协程传给asyncio.async函数或者是asyncio.ensure_future,或者是loop.create_task
3 在spin函数中,执行在终端输出旋转指针。并通过asyncio.sleep(0.1)的方式让出控制权,会到主循环
4 此时来到supervisor函数。此时进入slow_function,在slow_function中asyncio.sleep(3)进行休眠,并在休眠期把控制权交给主循环。此时主循环会将控制权又交给spin函数。
5 3秒休眠结束后,返回42,并通过spinner.cancel函数取消spin的task,取消后会在协程当前暂停的yield出抛出asyncio.CancelledError异常。至此整个程序运行完毕。
我们继续来看下用asyncio来实现图片下载的程序
DEST_URL='downloads/'
BASE_URL1='http://seopic.699pic.com/photo/40011'
down_list=('8840.jpg_wh1200','7347.jpg_wh1200','6876.jpg_wh1200','6876.jpg_wh1200')
def save_flag(img,filename):
path=os.path.join(DEST_URL,filename)
with open(path,'wb') as f:
f.write(img)
@asyncio.coroutine
def get_flag(cc):
url='{}/{cc}.jpg'.format(BASE_URL1,cc=cc)
print(url)
resp = yield from aiohttp.ClientSession().get(url)
print (resp.status)
image = yield from resp.read()
return image
def show(text):
print(text)
sys.stdout.flush()
@asyncio.coroutine
def download_one(cc):
image = yield from get_flag(cc)
show(cc)
save_flag(image, cc + '.jpg')
return cc
def download_many(cc_list):
loop=asyncio.get_event_loop()
to_do=[download_one(cc) for cc in sorted(cc_list)]
wait_coro=asyncio.wait(to_do)
res,_=loop.run_until_complete(wait_coro)
loop.close()
return len(res)
def main(download_many):
t1=time.time()
count=download_many(down_list)
elapsed=time.time()-t1
msg='\n{} flags downloaded in {:.2f}s'
print(msg.format(count,elapsed))
if __name__=="__main__":
main(download_many)
在这里我们用yield from aiohttp.ClientSession().get(url)
代替了request函数。因为request函数是个IO阻塞型的函数。注意aiohttp必须要安装才能使用。书上写的是用aiohttp.request(‘GET’,url)方法,但是我在实际使用的时候发现无法下载,提示如下错误:
client_session: <aiohttp.client.ClientSession object at 0x7fac75231f28>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7fac75231dd8>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7fac75231eb8>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7fac75231f28>
Task exception was never retrieved
future: <Task finished coro=<download_one() done, defined at /home/zhf/py_prj/function_test/asy_try.py:51> exception=TypeError("'_SessionRequestContextManager' object is not iterable",)>
Traceback (most recent call last):
File "/home/zhf/py_prj/function_test/asy_try.py", line 53, in download_one
image = yield from get_flag(cc)
File "/home/zhf/py_prj/function_test/asy_try.py", line 39, in get_flag
resp=yield from aiohttp.request('GET',url)
TypeError: '_SessionRequestContextManager' object is not iterable
Task exception was never retrieved
future: <Task finished coro=<download_one() done, defined at /home/zhf/py_prj/function_test/asy_try.py:51> exception=TypeError("'_SessionRequestContextManager' object is not iterable",)>
在网上搜了下,推荐使用aiohttp.ClientSession().get进行下载。这个函数能保证相关的TCP资源能够得到释放,比如TCP链接
在这里download_one和get_flag都用到了协程, 意味着必须像协程那样驱动,这样才能把控制权交还给时间循环
asyncio.wait分别把各个协程包装进一个task对象,最后的结果是,wait处理的所有对象都通过某种方式变成Future类的实例,wait是协程函数,因此返回的是一个协程或生成器对象。为了驱动协程,我们把协程传给run_until_completed方法。
运行时间:
5 flags downloaded in 0.34s
下面继续来优化下这个下载程序。
首先将代码改动下,在保存图片的时候将大小放大1000倍
def save_flag(img,filename):
path=os.path.join(DEST_URL,filename)
with open(path,'wb') as f:
f.write(img*1000)
5 flags downloaded in 13.46s
下载的图片大小如下所示:一张图片493M

那么耗时的时间呢.总共耗费了13.46秒的时间。速率降低了40多倍
5 flags downloaded in 13.46s
原因是什么呢。原因就在与save_flag是一个阻塞性的函数(f.write)。save_flag函数阻塞了客户代码与asyncio事件循环共用的唯一线程。因此保存文件的时候,整个程序都会冻结。那么解决办法就是使用run_in_executor方法。download_one代码修改如下:
@asyncio.coroutine
def download_one(cc):
image = yield from get_flag(cc)
show(cc)
loop=asyncio.get_event_loop()
loop.run_in_executor(None,save_flag,image,cc+'.jpg')
return cc
修改之后总共耗时1.5秒
5 flags downloaded in 1.50s
流畅python学习笔记第十八章:使用asyncio包处理并发(二)的更多相关文章
- 流畅python学习笔记第十八章:使用asyncio包处理并发(一)
首先是线程与协程的对比.在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别.在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用. 知识点一:当一个进程启 ...
- 流畅python学习笔记第十八章:使用asyncio编写服务器
在这一章中,将使用asyncio写一个TCP服务器.这个服务器的作用是通过规范名称查找Unicode字符,来看下代码: import asyncio from charfinder import Un ...
- 流畅的python第十八章使用asyncio包处理并发
对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关系asyncio.Future 类与 concurrent.futures.Future 类之间的区别摒弃线程或进程, ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- Python学习笔记(十)
Python学习笔记(十): 装饰器的应用 列表生成式 生成器 迭代器 模块:time,random 1. 装饰器的应用-登陆练习 login_status = False # 定义登陆状态 def ...
- Python学习笔记(十五):类基础
以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-24 23:59 学习笔记 1,Python中的大多 ...
- Python学习笔记(十四):模块高级
以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...
- 流畅python学习笔记:第十七章:并发处理二
本章讨论python3.2引入的concurrent.futures模块.future是中文名叫期物.期物是一种对象,表示异步执行的操作 在很多任务中,特别是处理网络I/O.需要使用并发,因为网络有很 ...
- 流畅python学习笔记:第十五章:上下文管理器
在开始本章之前,我们首先来谈谈try-excep..final模块.在Python中,进行异常保护的最多就是用try..except..final.首先来看下下面的代码.进行一个简单的除法运算.为了防 ...
随机推荐
- workflow engine Ruote初体验之二(通用属性)
罗列一下表达式所支持的属性: :timeout :if/ unless :forget :lose :flank :on_error :on_cancel :on_timeout :tag :filt ...
- Java Web开发(JSP、Servlet)乱码的一揽子解决方案
千万不要看网上那些杂七杂八的解决乱码的文章,解决乱码最好的方法是(没有之一):在所有地方统一采用UTF-8编码. 这其中包括: 1 - 工程 如果使用的是Eclipse,那么打开Preference, ...
- 单页js文件访问数据库
最原始的编程方式,业务逻辑混杂在html中 <%@ page language="java" import="java.util.*" pageEncod ...
- 使用assembly将maven项目pom.xml中的jar包打包
参考官方网站:http://maven.apache.org/plugins/maven-assembly-plugin/usage.html 方法一:将pom.xml引入的jar包打到zip文件夹中 ...
- springnodejs
作者 : solq 最新文档请看 http://www.springnodejs.com 本文不再更新 blog : http://www.cnblogs.com/solq/p/3574640.htm ...
- Locust压力测试Odoo
Table of Contents 编写测试任务集 TaskSet 运行 Locust 分布式 运行 master 运行 slave. Locust 是个伸缩性很好的压力测试框架,OdooLo ...
- apue学习笔记(第十四章 高级I/O)
本章涵盖了从多概念和函数:非阻塞I/O.记录锁.I/O多路转换.异步I/O.readv和writev函数以及存储映射I/O 非阻塞I/O 非阻塞I/O使我们可以发出open.read和write这样的 ...
- TimeSpan时间间隔
一个TimeSpan对象都表示一个时间间隔 (持续时间的时间或时间),单位为正数或负数的天数. 小时. 分钟. 秒和小数部分的第二个数字.TimeSpan结构还可以用于表示一天时间,但仅,如果与某一特 ...
- xml.etree.ElementTree模块的封装
转载:https://www.cnblogs.com/hongten/p/hongten_python_xml_etree_elementtree.html 1 # -*- coding: utf-8 ...
- android开发系列之使用xml自定义控件
在android开发的过程中,有的时候面对多个Activity里面一些相同的布局,我们需要写多次相同的代码,同时这种方法给我们的项目维护也带来了很大不便.那么有没有一种可行的办法能够将Activity ...