并行和并发不是相同的概念。在某些情况下,并发更加强大。
这篇文章将帮助你利用Asyncio实现最佳的并发效果。Python是一种容易上手的语言,但要精通它需要理解很多概念。在python2中,经常使用多进程模块进行并行处理的方法。
Python还提供了使用Asyncio模块从3.4版本开始与并发一起工作的能力。
对于来自JavaScript背景的人来说,这个概念可能并不陌生,但对于从Python 2.7转过来的人来说,Asyncio可能很难理解,尤其是并发和并行的区别。然而,由于它日益流行,掌握Asyncio确实变得非常重要。
在这篇文章中,我希望以最简单的方式解释Asyncio,消除一些困惑。那么,什么是Asyncio?根据Python文档的解释,"Asyncio是一个使用async/await语法编写并发代码的库"。但要真正理解Asyncio,我们首先需要了解阻塞调用。
那么,什么是阻塞调用?简单来说,阻塞调用是指需要花费时间完成但不需要CPU处理能力的过程。例如,time.sleep(10)就是一个很好的例子。这个调用告诉计算机休眠10秒钟,不做其他任何事情。
另一个阻塞调用的例子是当你等待来自服务器的响应时,你的CPU处于空闲状态。举个更实际的例子,假设你需要完成一些任务——洗衣服、做饭和烘烤蛋糕。为了方便起见,假设每个任务需要60分钟。如果你按顺序完成这些任务,你需要三个小时才能完成所有任务。通常情况下,当我们需要完成这些任务时,我们会在它们之间切换。例如,你可以在洗衣机运行时做饭。当锅在炉子上或蛋糕在烤箱里时,你也是处理其他事务的。
因此,在这里,你消除了任务中的等待部分,也就是阻塞调用。
这样,你只需要总共花费一个小时来完成这三个任务。Asyncio只是一个框架,它允许你的计算机做到这一点。你可以要求单个进程在遇到阻塞调用时切换并开始处理另一个任务。Asyncio的实际应用让我们从一个简单的例子开始,以了解Asyncio的工作原理。假设我们想要执行以下代码,它在睡眠一段时间后返回一些内容。这个例子与我们之前看到的那些有阻塞调用的任务非常相似。这里的阻塞调用是time.sleep。如果我们按顺序执行,
import time
def return_something():
time.sleep(1)
return "hello!"
def main():
result = []
for i in range(3):
result.append(return_something())
return result
if __name__ == "__main__":
import time
s = time.perf_counter()
result = main()
elapsed = time.perf_counter() - s
print(result)
print(f"Executed in {elapsed:0.2f} seconds.")
--------------------------------------------------------------------
在顺序设置中,这段代码需要大约三秒钟才能运行。我们如何使用Asyncio重写这段代码呢?
import asyncioimport time
async def return_something():
await asyncio.sleep(1)
return "hello!"
async def run_multiple_times():
tasks = []
for i in range(3):
tasks.append(return_something())
result = await asyncio.gather(*tasks)
return result
if __name__ == "__main__":
s = time.perf_counter()
result = await run_multiple_times()
elapsed = time.perf_counter() - s
print(result)
print(f"Executed in {elapsed:0.2f} seconds.")
那么,这里发生了什么?这些await和async关键字是什么意思?
简单来说,async和await关键字是Python告诉单进程线程在遇到阻塞调用时切换任务的方式。
在这段代码中,我们首先使用async关键字定义了异步函数(通常称为协程)。在这个样板代码中,我们做了两件事:
- 定义了一个简单的异步函数return_something,其中包含一个阻塞调用。我们通过在阻塞调用sleep(1)前添加await来告诉Python在遇到阻塞调用时切换到另一个任务。
- 使用gather(*tasks)在run_multiple_times函数中多次运行该异步函数。这个函数也是异步的。
需要注意的一点是,我们使用的是asyncio.sleep(1)而不是time.sleep(1)。这是因为time.sleep是一个普通的Python函数,而我们只能等待协程(使用async关键字定义的异步函数)。此外,需要注意的是,调用任何异步函数都需要使用await关键字,就像在result = await run_multiple_times()和await asyncio.gather(*tasks)中看到的那样。
正是因为这个原因,上面的代码中的`tasks.append(return_something())`并不会立即运行`return_something()`,而只是将任务添加到任务列表中。
这可能看起来有点复杂,但实际上,在使用asyncio时,您可以直接使用上面的代码,并考虑以下三个步骤:
- 编写一个带有一些阻塞调用的异步函数,该函数执行某些操作。例如,获取URL的HTML响应或者在上面的例子中的`return_something`函数。
- 编写一个异步函数,该函数可以多次运行上述函数,将其添加到任务列表中,并使用`asyncio.gather`来执行这些任务。
- 使用`await`运行第二个函数。
这样,您就可以使用上述代码来使用asyncio,实现并发执行任务的功能。