gpt4 book ai didi

python - 异步: why isn't it non-blocking by default

转载 作者:太空宇宙 更新时间:2023-11-04 06:58:42 25 4
gpt4 key购买 nike

默认情况下,asyncio同步运行协程。如果它们包含阻塞的IO代码,则它们仍然等待其返回。解决方法是 loop.run_in_executor() ,它将代码转换为线程。如果某个线程在IO上阻塞,则另一个线程可以开始执行。这样您就不会浪费时间等待IO调用。

如果您在没有执行程序的情况下使用asyncio,则会失去这些加速。所以我想知道,为什么您必须显式使用执行程序。为什么不默认启用它们?
(在下文中,我将重点介绍http请求。但是,它们实际上仅作为示例。我对一般原则感兴趣。)

经过一番搜索,我发现aiohttp。这是一个实质上提供asynciorequests组合的库:非阻塞HTTP调用。对于执行程序,asynciorequests的行为几乎类似于aiohttp。有没有实现新库的理由,您是否因使用执行程序而支付性能损失?

回答了这个问题:Why doesn't asyncio always use executors?
Mikhail Gerasimov向我解释说,执行程序将加速OS线程,并且它们可能变得昂贵。因此,不要将它们作为默认行为是有道理的。 aiohttp比在执行程序中使用requests模块更好,因为它仅提供协程提供非阻塞代码。

这使我想到了这个问题。 aiohttp广告自己为:

Asynchronous HTTP Client/Server for asyncio and Python.



那么 aiohttp是基于 asyncio的?那么 asyncio为什么不只提供协程提供非阻塞代码呢?那将是理想的默认值。

还是 aiohttp本身实现了这个新的事件循环(没有OS线程)?
在那种情况下,我不明白为什么他们会根据 asyncio做广告。 Async/await是一种语言功能。 Asyncio是一个事件循环。而且,如果 aiohttp有其自己的事件循环,则应该与 asyncio几乎没有交集。实际上,我认为这样的事件循环将比http请求具有更大的功能。

最佳答案

asyncio是异步的,因为协程是自愿合作的。 在编写所有 asyncio代码时必须牢记合作,这就是重点。否则,您也可以单独使用线程来实现并发。

您不能在执行程序中运行“阻塞”功能(非协程函数或无法配合使用的方法),因为您不能仅仅假设代码可以在单独的执行程序线程中运行。甚至即使它需要在执行程序中运行。

Python标准库中充满了非常有用的代码,asyncio项目将要使用这些代码。标准库的大部分由常规的“阻止”功能和类定义组成。他们工作很快,所以即使他们“阻塞”,他们也会在合理的时间内返回。

但是大多数代码也不是线程安全的,通常不需要。但是,一旦asyncio将自动在执行程序中运行所有此类代码,那么您将无法再使用非线程安全的函数。此外,创建线程以运行同步代码不是免费的,创建线程对象会花费时间,并且您的操作系统也不允许您运行无限数量的线程。标准库函数和方法的加载速度很快,为什么只执行代码并完成它会更快得多,为什么要在单独的线程中运行str.splitlines()urllib.parse.quote()

您可能会说这些功能并未受到您的标准的阻碍。您没有在此处定义“阻塞”,但“阻塞”仅表示:不会自动让步。如果将其缩小为在必须等待某件事并且计算机可能正在做其他事情时不会自动让步相反,接下来的问题将是您如何检测应该产生的

答案是,你做不到。 time.sleep()是您要屈服于循环的阻塞函数,但这是C函数的调用。 Python无法知道time.sleep()将阻塞更长的时间,因为调用time.sleep()的函数将在全局命名空间中查找名称time,然后在名称查找结果中查找属性sleep,仅在实际执行time.sleep()时表达。由于在执行过程中可以随时更改Python的 namespace ,因此,在实际执行函数之前,您不知道time.sleep()将做什么。

您可以说time.sleep()实现应在调用时自动产生,但随后您必须开始标识所有此类函数。而且,您不必打补丁的地方数量也没有限制,而且您永远不可能知道所有地方。当然不适合第三方库。例如, python-adb project使用libusb1库为您提供了与Android设备的同步USB连接。那不是标准的I/O代码路径,那么Python怎么会知道创建和使用这些连接是产生 yield 的好地方?

因此,您不能仅假设代码需要在执行程序中运行,并非所有代码都可以在执行程序中运行,因为它不是线程安全的,并且Python无法检测到何时代码被阻塞并且应该真正产生。

那么asyncio下的协程如何协作?通过每个需要与其他任务同时运行的逻辑代码使用task objects,并通过使用future objects向任务发信号通知当前逻辑代码希望将控制权让给其他任务。这就是使异步asyncio代码异步,自愿让出控制的原因。当循环将控制权从一个任务中选出时,该任务将执行协程调用链的一个“步骤”,直到该调用链产生一个将来的对象,此时该任务将唤醒回调添加到将来的对象“完成” '回调列表,并将控制权返回到循环。在以后的某个时刻,当将来标记为完成时,将运行唤醒回调,并且该任务将执行另一个协程调用链步骤。

其他负责将将来的对象标记为已完成。当您使用asyncio.sleep()时,将在特定时间运行的回调被赋予循环,该回调将把asyncio.sleep()将来标记为完成。当您使用stream object执行I/O时(在UNIX上),循环将使用 select calls来检测何时完成I/O操作时唤醒将来的对象。当您使用lock or other synchronisation primitive时,同步原语将在适当的时候维护一堆 future 以将其标记为“完成”(等待锁?将 future 添加到桩中。释放持有的锁?从桩中选择下一个 future 并将其标记为完成,因此等待锁的下一个任务可以唤醒并获取锁,依此类推)。

将阻塞的同步代码放入执行程序中只是这里合作的另一种形式。在项目中使用asyncio时,开发人员应确保使用给定的工具来确保协程协同工作。您可以自由地在文件上使用阻塞open()调用而不是使用流,并且当您知道需要在单独的线程中运行代码以避免阻塞时间太长时,可以自由使用执行程序。

最后但并非最不重要的一点是,使用asyncio的全部目的是尽可能避免使用线程。使用线程有缺点。代码必须是线程安全的(控制可以在任何地方的线程之间切换,因此访问共享数据的两个线程应格外小心,“小心”可能意味着代码变慢了)。线程无论是否有事都要执行。在所有等待I/O发生的固定数量的线程之间切换控制会浪费CPU时间,其中asyncio循环可自由查找未等待的任务。

关于python - 异步: why isn't it non-blocking by default,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53264314/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com