赞
踩
Python 3.5 引入了两个新关键字: async
和 await
。这些看似神奇的关键字完全可以在没有任何线程的情况下实现类似线程的并发。在本教程中,我们将介绍异步编程的原因,并通过构建我们自己的小型异步类框架来说明Python的 async/await
关键字如何在内部工作。
要了解异步编程的动机,我们首先必须了解是什么限制了我们的代码运行速度。理想情况下,我们希望我们的代码以光速运行,立即跳过我们的代码,没有任何延迟。然而,由于两个因素,实际上代码运行速度要慢得多:
当我们的代码在等待 IO 时,CPU 基本上是空闲的,等待某个外部设备响应。通常,内核会检测到这一点并立即切换到执行系统中的其他线程。因此,如果我们想加快处理一组 IO 密集型任务,我们可以为每个任务创建一个线程。当其中一个线程停止,等待 IO 时,内核将切换到另一个线程继续处理。
这在实践中效果很好,但有两个缺点:
例如,如果我们想要执行 10,000 个任务,我们要么必须创建 10,000 个线程,这将占用大量 RAM,要么我们需要创建较少数量的工作线程并以较少的并发性执行任务。此外,最初生成这些线程会占用 CPU 时间。
由于内核可以随时选择在线程之间切换,因此我们代码中的任何时候都可能出现相互竞争。
在传统的基于同步线程的代码中,内核必须检测线程何时是IO绑定的,并选择在线程之间随意切换。使用 Python 异步,程序员使用关键字 await
确认声明 IO 绑定的代码行,并确认授予执行其他任务的权限。例如,考虑以下执行Web请求的代码:
async def request_google(): reader, writer = await asyncio.open_connection('google.com', 80) writer.write(b'GET / HTTP/2\n\n') await writer.drain() response = await reader.read() return response.decode()
在这里,在这里,我们看到该代码在两个地方 await
。因此,在等待我们的字节被发送到服务器( writer.drain()
)时,在等待服务器用一些字节( reader.read()
)回复时,我们知道其他代码可能会执行,全局变量可能会更改。然而,从函数开始到第一次等待,我们可以确保代码逐行运行,而不会切换到运行程序中的其他代码。这就是异步的美妙之处。
asyncio
是一个标准库,可以让我们用这些异步函数做一些有趣的事情。例如,如果我们想同时向Google执行两个请求,我们可以:
async def request_google_twice(): response_1, response_2 = await asyncio.gather(request_google(), request_google()) return response_1, response_2
当我们调用 request_google_twice()
时,神奇的 asyncio.gather
会启动一个函数调用,但是当我们调用时 await writer.drain()
,它会开始执行第二个函数调用,这样两个请求就会并行发生。然后,它等待第一个或第二个请求的 writer.drain()
调用完成并继续执行该函数。
最后,有一个重要的细节被遗漏了: asyncio.run
。要从常规的 [同步] Python 函数实际调用异步函数,我们将调用包装在 asyncio.run(...)
:
async def async_main(): r1, r2 = await request_google_twice() print('Response one:', r1) print(&#
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。