Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发。
例6:协程异步(非阻塞)模式的 HTTP 下载
这次我们把协程的方案也放进来。还记得我刚才说过,使用协程库里的 monkey.patch_all() 方法可以把任何同步代码转换为异步,当然也包括这个任务里用到的 requests 库。
现在 requests.get(url) 函数可以通过协程的事件循环来切换上下文,转换成非阻塞模式了,也就不用写 yield 了。在任务执行部分,我们使用协程来启动两个任务,最后用 joinall() 方法等待它们执行完毕。
"""
example_6.py
Just a short example demonstrating a simple state machine in Python
This version is doing actual work, downloading the contents of
URL's it gets from a queue. It's also using gevent to get the
URL's in an asynchronous manner.
"""
import gevent
from gevent import monkey
monkey.patch_all()
import queue
import requests
from lib.elapsed_time import ET
def task(name, work_queue):
while not work_queue.empty():
url = work_queue.get()
print(f'Task {name} getting URL: {url}')
et = ET()
requests.get(url)
print(f'Task {name} got URL: {url}')
print(f'Task {name} total elapsed time: {et():.1f}')
def main():
"""
This is the main entry point for the program
"""
# create the queue of 'work'
work_queue = queue.Queue()
# put some 'work' in the queue
for url in [
"http://google.com",
"http://yahoo.com",
"http://linkedin.com",
"http://shutterfly.com",
"http://mypublisher.com",
"http://facebook.com"
]:
work_queue.put(url)
# run the tasks
et = ET()
tasks = [
gevent.spawn(task, 'One', work_queue),
gevent.spawn(task, 'Two', work_queue)
]
gevent.joinall(tasks)
print()
print(f'Total elapsed time: {et():.1f}')
if __name__ == '__main__':
main()
"""
译者注,运行结果:
Task One getting URL: http://google.com
Task Two getting URL: http://yahoo.com
Task Two got URL: http://yahoo.com
Task Two total elapsed time: 1.7
Task Two getting URL: http://linkedin.com
timeout
Task One got URL: http://google.com
Task One total elapsed time: 2.0
Task One getting URL: http://shutterfly.com
Task Two got URL: http://linkedin.com
Task Two total elapsed time: 1.2
Task Two getting URL: http://mypublisher.com
Task Two got URL: http://mypublisher.com
Task Two total elapsed time: 2.9
Task Two getting URL: http://facebook.com
Task One got URL: http://shutterfly.com
Task One total elapsed time: 5.7
timeout
Task Two got URL: http://facebook.com
Task Two total elapsed time: 2.0
Total elapsed time: 7.9
"""
仔细看看结尾的总耗时和每个网址分别耗时,显尔易见,总耗时小于每个网址的单独耗时之和。
因为每个请求都是异步的,这就帮助我们有效的利用了 CPU 资源来同时处理多个请求。
例7:基于 Twisted 的异步(非阻塞) HTTP 下载
下面这个例子我们改用 Twisted 框架 来实现刚才协程模块的任务,用非阻塞模式下载网页内容。
Twisted 非常强大,它采用完全不同的方法来创建异步程序。协程用修改模块的方法将同步转换为异步,Twisted 则提供了自己的一套函数和方法来实现同样的效果。
例6 中是用修补 requests.get(url) 的方式获取页面,现在我们改用 Twisted 提供的 getPage(url) 方法。
在下面的代码中,装饰器 @defer.inlineCallbacks 和 yield getPage(url) 一起使用,是为了将上下文切换到 Twisted 的事件循环当中。
在协程中事件循环是隐式的,而在 Twisted 中是通过程序结尾的 reactor.run() 语句来显式调用的。
"""
example_7.py
Just a short example demonstrating a simple state machine in Python
This version is doing actual work, downloading the contents of
URL's it gets from a work_queue. This version uses the Twisted
framework to provide the concurrency
"""
from twisted.internet import defer
from twisted.web.client import getPage
from twisted.internet import reactor, task
import queue
from lib.elapsed_time import ET
@defer.inlineCallbacks
def my_task(name, work_queue):
try:
while not work_queue.empty():
url = work_queue.get()
print(f'Task {name} getting URL: {url}')
et = ET()
yield getPage(url)
print(f'Task {name} got URL: {url}')
print(f'Task {name} total elapsed time: {et():.1f}')
except Exception as e:
print(str(e))
def main():
"""
This is the main entry point for the program
"""
# create the work_queue of 'work'
work_queue = queue.Queue()
# put some 'work' in the work_queue
for url in [
b"http://google.com",
b"http://yahoo.com",
b"http://linkedin.com",
b"http://shutterfly.com",
b"http://mypublisher.com",
b"http://facebook.com"
]:
work_queue.put(url)
# run the tasks
et = ET()
defer.DeferredList([
task.deferLater(reactor, 0, my_task, 'One', work_queue),
task.deferLater(reactor, 0, my_task, 'Two', work_queue)
]).addCallback(lambda _: reactor.stop())
# run the event loop
reactor.run()
print()
print(f'Total elapsed time: {et():.1f}')
if __name__ == '__main__':
main()
"""
译者注,运行结果:
Task One getting URL: b'http://google.com'
Task Two getting URL: b'http://yahoo.com'
Task Two got URL: b'http://yahoo.com'
Task Two total elapsed time: 2.1
Task Two getting URL: b'http://linkedin.com'
Task Two got URL: b'http://linkedin.com'
Task Two total elapsed time: 1.2
Task Two getting URL: b'http://shutterfly.com'
Task Two got URL: b'http://shutterfly.com'
Task Two total elapsed time: 4.2
Task Two getting URL: b'http://mypublisher.com'
Task Two got URL: b'http://mypublisher.com'
Task Two total elapsed time: 5.0
Task Two getting URL: b'http://facebook.com'
User timeout caused connection failure.
User timeout caused connection failure.
Total elapsed time: 42.5
"""
这个运行结果和前面用协程方法得到的一样,总耗时小于每个网址访问单独耗时的总和。
例8:基于 Twisted 回调方法的异步(非阻塞)HTTP 下载
这个例子我仍然使用 Twisted 这个库,只不过换了一个比较传统的方式。
比如说,上面我们采用了 @defer.inlineCallbacks / yield的编码形式,现在我们要改成显式回调。所谓回调函数就是一个传递给系统的用来响应某个事件函数。下面例子中的 success_callback() 函数就是传递给 Twisted 用来响应 getPage(url) 事件的回调函数。
注意,这个例子里的 my_task() 任务函数已经不再需要 @defer.inlineCallbacks 装饰器了。另外,这个函数还生成了一个延迟变量,我们简称它为 d ,它是 getPage(url) 函数的返回值。
延迟变量是 Twisted 处理异步编程的方式,也是回调函数所必需的。一旦延迟被“触发”(也就是getPage(url) 完成时),就立刻调用回调函数,并传入指定参数。
程序运行结果和上面两个示例一样,总耗时小于每个访问分别耗时之和。
你想用协程还是Twisted的方式来实现都可以,因人而异。两种方案都提供了强大的功能帮助开发者来创建异步代码。
结论
希望以上内容能帮助你了解掌握异步编程的应用场景和工作方式。如果你要计算 Pi 的小数点后 100 万位,那恐怕异步起不了什么作用。
然而,如果你要实现一个服务器,或者其它一些需要大量读写操作的代码,那你肯定能感受到什么叫质的飞跃。这项技术非常强大,一定能让你的程序性能上一个新台阶。英文原文:https://dbader.org/blog/understanding-asynchronous-programming-in-python#.
译者:WDatou