Python黑魔法之协程/异步IO
<p style="text-align:center"><img src="https://simg.open-open.com/show/e6e1fd74659662ff5bf16b051ffda8c0.png"></p> <p style="text-align:center">协程与异步IO</p> <h2>引言</h2> <p>随着node.js的盛行,相信大家今年多多少少都听到了异步编程这个概念。Python社区虽然对于异步编程的支持相比其他语言稍显迟缓,但是也在Python3.4中加入了 asynico ,在Python3.5上又提供了async/await语法层面的支持,刚正式发布的 Python3.6 中asynico也已经由临时版改为了稳定版。下面我们就基于 <strong>Python3.4+</strong> 来了解一下异步编程的概念以及asyncio的用法。</p> <h2>什么是协程</h2> <p>通常在Python中我们进行并发编程一般都是使用多线程或者多进程来实现的,对于计算型任务由于GIL的存在我们通常使用多进程来实现,而对与IO型任务我们可以通过线程调度来让线程在执行IO任务时让出GIL,从而实现表面上的并发。</p> <p>其实对于IO型任务我们还有一种选择就是协程, <strong>协程是运行在单线程当中的“并发”</strong> ,协程相比多线程一大优势就是省去了多线程之间的切换开销,获得了更大的运行效率。Python中的asyncio也是基于协程来进行实现的。在进入asyncio之前我们先来了解一下Python中怎么通过生成器进行协程来实现并发。</p> <p>example1</p> <p>我们先来看一个简单的例子来了解一下什么是协程(coroutine),对生成器不了解的朋友建议先看一下 Stackoverflow上面的这篇高票提问 。</p> <pre> <code class="language-python">>>> def coroutine(): ... reply = yield 'hello' ... yield reply ... >>> c = coroutine() >>> next(c) 'hello' >>> c.send('world') 'world'</code></pre> <p>example2</p> <p>下面这个程序我们要实现的功能就是 <strong>模拟多个学生同时向一个老师提交作业</strong> ,按照传统的话我们或许要采用多线程/多进程,但是这里我们可以采用生成器来实现协程用来模拟并发。</p> <p>如果下面这个程序读起来有点困难,可以直接跳到后面部分,并不影响阅读,等你理解协程的本质,回过头来看就很简单了。</p> <pre> <code class="language-python">from collections import deque def student(name, homeworks): for homework in homeworks.items(): yield (name, homework[0], homework[1]) # 学生"生成"作业给老师 class Teacher(object): def __init__(self, students): self.students = deque(students) def handle(self): """老师处理学生的作业""" while len(self.students): student = self.students.pop() try: homework = next(student) print('handling', homework[0], homework[1], homework[2]) except StopIteration: pass else: self.students.appendleft(student)</code></pre> <p>下面我们来调用一下这个程序。</p> <pre> <code class="language-python">Teacher([ student('Student1', {'math': '1+1=2', 'cs': 'operating system'}), student('Student2', {'math': '2+2=4', 'cs': 'computer graphics'}), student('Student3', {'math': '3+3=5', 'cs': 'compiler construction'}) ]).handle()</code></pre> <p>这是输出结果,我们仅仅只用了一个简单的生成器就实现了并发(concurrence),注意不是并行(parallel),因为我们的程序仅仅是运行在一个单线程当中。</p> <pre> <code class="language-python">handling Student3 cs compiler construction handling Student2 cs computer graphics handling Student1 cs operating system handling Student3 math 3+3=5 handling Student2 math 2+2=4 handling Student1 math 1+1=2</code></pre> <h2>使用asyncio模块实现协程</h2> <p>从Python3.4开始asyncio模块加入到了标准库,通过asyncio我们可以轻松实现协程来完成异步IO操作。</p> <p>解释一下下面这段代码,我们创造了一个协程 <em>display_date(num, loop)</em> ,然后它使用 <a href="/misc/goto?guid=4959730533494010389" rel="nofollow,noindex">yield from</a> 关键字来等待 <em>asyncio.sleep(2)</em> 的返回结果,对于asnycio.sleep(2)可以把它理解为一个IO操作,实际上它内部也是一个协程,但是要等待2s的时间后才返回结果。而在这等待的2s之间它会让出CPU的执行权,直到asyncio.sleep(2)返回结果。</p> <pre> <code class="language-python"># coroutine.py import asyncio import datetime @asyncio.coroutine # 定义一个协程 def display_date(num, loop): end_time = loop.time() + 10.0 while True: print("Loop: {} Time: {}".format(num, datetime.datetime.now())) if (loop.time() + 1.0) >= end_time: break yield from asyncio.sleep(2) # 这里可以理解为一个2s的IO中断 loop = asyncio.get_event_loop() # 获取一个event_loop tasks = [ asyncio.ensure_future(display_date(1, loop)), # 开启协程1 asyncio.ensure_future(display_date(2, loop)) # 开启协程2 ] loop.run_until_complete(asyncio.gather(*tasks)) loop.close()</code></pre> <p>下面是运行结果,注意到并发的效果没有,程序从开始到结束只用大约10s,而在这里我们并没有使用任何的多线程/多进程代码。在实际项目中你可以将asyncio.sleep(secends)替换成相应的IO任务,比如数据库/磁盘文件读写等操作。</p> <pre> <code class="language-python">ziwenxie :: ~ » python coroutine.py Loop: 1 Time: 2016-12-19 16:06:46.515329 Loop: 2 Time: 2016-12-19 16:06:46.515446 Loop: 1 Time: 2016-12-19 16:06:48.517613 Loop: 2 Time: 2016-12-19 16:06:48.517724 Loop: 1 Time: 2016-12-19 16:06:50.520005 Loop: 2 Time: 2016-12-19 16:06:50.520169 Loop: 1 Time: 2016-12-19 16:06:52.522452 Loop: 2 Time: 2016-12-19 16:06:52.522567 Loop: 1 Time: 2016-12-19 16:06:54.524889 Loop: 2 Time: 2016-12-19 16:06:54.525031 Loop: 1 Time: 2016-12-19 16:06:56.527713 Loop: 2 Time: 2016-12-19 16:06:56.528102</code></pre> <p>在Python3.5中为我们提供更直接的对协程的支持,引入了async/await关键字,上面的代码我们可以这样改写,使用async代替了@asyncio.coroutine,使用了await代替了yield from,这样我们的代码变得更加简洁可读。</p> <pre> <code class="language-python">import asyncio import datetime async def display_date(num, loop): end_time = loop.time() + 10.0 while True: print("Loop: {} Time: {}".format(num, datetime.datetime.now())) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(2) loop = asyncio.get_event_loop() # 获取一个event_loop tasks = [ asyncio.ensure_future(display_date(1, loop)), # 开启协程1 asyncio.ensure_future(display_date(2, loop)) # 开启协程2 ] loop.run_until_complete(asyncio.gather(*tasks)) loop.close()</code></pre> <h2>在爬虫中使用asyncio来实现异步IO</h2> <p>下面我们来通过一个例子来了解怎么在Python爬虫项目中使用asyncio。</p> <p>example1</p> <pre> <code class="language-python">import asyncio import requests async def spider(loop): # 调用request库的get方法,返回一个future对象 future1 = loop.run_in_executor(None, requests.get, 'https://www.python.org/') future2 = loop.run_in_executor(None, requests.get, 'http://httpbin.org/') # await future对象,实际上就是一个网络IO延迟 response1 = await future1 response2 = await future2 print(response1.text) print(response2.text) loop = asyncio.get_event_loop() # 得到一个event_loop loop.run_until_complete(spider(loop))</code></pre> <p>关于future对象,asyncio中的futrue和 标准库中的concurrent.future 用法基本一致,建议大家去详细了解一下,我晚点再来更新。</p> <p>文章未完,待更新。</p> <h2>References</h2> <p><a href="/misc/goto?guid=4958968331496731648" rel="nofollow,noindex">DOCUMENTION OF ASYNCIO</a></p> <p><a href="/misc/goto?guid=4959730533615164021" rel="nofollow,noindex">COROUTINES AND ASYNC/AWAIT</a></p> <p><a href="/misc/goto?guid=4959730533694037320" rel="nofollow,noindex">STACKOVERFLOW</a></p> <p><a href="/misc/goto?guid=4959730533777558705" rel="nofollow,noindex">PyMOTW-3</a></p> <p> </p> <p>来自:http://www.jianshu.com/p/4e048726b613</p> <p> </p>
本文由用户 chl1988 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!