为什么德福没有阻止它

2024-04-26 23:12:18 发布

您现在位置:Python中文网/ 问答频道 /正文

默认情况下,asyncio同步运行协同程序。如果它们包含阻塞IO代码,它们仍然等待它返回。解决这一问题的方法是^{},它将代码转换为线程。如果一个线程阻塞IO,另一个线程可以开始执行。这样你就不用浪费时间等IO电话了。在

如果使用没有执行器的asyncio,则会丢失这些加速。所以我想知道,为什么要显式地使用执行器。为什么不在默认情况下启用它们? (在下面,我将重点讨论http请求。但它们实际上只是一个例子。我对一般原理感兴趣。)

经过一番搜索,我找到了aiohttp。它是一个基本上提供asyncio和{}的组合:非阻塞HTTP调用。对于执行器,asyncio和{}的行为与aiohttp非常相似。有没有理由实现一个新的库,你会因为使用执行器而付出性能惩罚吗?在

这个问题得到了回答:Why doesn't asyncio always use executors? mikhailgerasimov向我解释说,执行者将启动操作系统线程,它们可能会变得昂贵。因此,不把它们作为默认行为是有道理的。aiohttp比在executor中使用requests模块要好,因为它只提供协程的非阻塞代码。在

这就引出了这个问题。aiohttp宣传自己为:

Asynchronous HTTP Client/Server for asyncio and Python.

那么aiohttp是基于asyncio?那么asyncio为什么不提供只有协程的非阻塞代码呢?这将是理想的违约。在

或者aiohttp本身实现了这个新的事件循环(没有操作系统线程)? 在这种情况下,我不明白为什么他们会以asyncio为基础来宣传自己。Async/await是一种语言特性。Asyncio是一个事件循环。如果aiohttp有自己的事件循环,那么与asyncio应该没有多少交集。实际上,我认为这样的事件循环比http请求要大得多。在


Tags: 方法代码ioasynciohttpaiohttp时间事件
2条回答

asyncio是异步的,因为协同路由是自愿合作的。所有asyncio代码的编写必须考虑到合作,这就是全部要点。否则,您还可以专门使用线程来实现并发。在

你不能在执行器中运行“阻塞”函数(非协同程序函数或不合作的方法),因为你不能仅仅假设代码可以在单独的执行器线程中运行。或者即使它需要在执行器中运行。在

Python标准库中有很多非常有用的代码,asyncio项目会希望利用这些代码。大多数标准库由常规的“阻塞”函数和类定义组成。他们的工作很快,所以即使他们'阻止',他们返回合理的时间。在

但是大多数代码也不是线程安全的,通常不需要。但是一旦asyncio在执行器中自动运行所有这样的代码,那么您就不能再使用非线程安全函数了。此外,创建一个线程来运行同步代码并不是免费的,创建线程对象需要花费时间,而且您的操作系统也不会允许您运行无限多个线程。标准库函数和方法的加载是fast,为什么要在单独的线程中运行str.splitlines()或{},而只执行代码并完成它会快得多?在

您可能会说,这些函数没有被您的标准阻塞。你没有在这里定义“阻塞”,但是“阻塞”只是指:不会自愿屈服。。如果我们把这个范围缩小到在它必须等待某个东西并且计算机可能正在做其他事情时不会自动屈服,那么下一个问题就是您将如何检测到它应该已经产生了?在

答案是,你做不到。time.sleep()是一个阻塞函数,你想让循环为,但那是一个C函数调用。Python无法知道time.sleep()将要阻塞更长时间,因为调用time.sleep()的函数将在全局命名空间中查找名称time,然后在名称查找结果中查找属性sleep,只在实际执行time.sleep()表达式时。因为Python的名称空间在执行过程中的任何时候都可以改变,所以在实际执行函数之前,您不知道time.sleep()将做什么。在

您可以说time.sleep()实现在被调用时应该自动生成,但是之后您必须开始标识所有这样的函数。你需要修补的地方没有限制,你不可能知道所有的地方。当然不适合第三方图书馆。例如,^{} project提供了一个到Android设备的同步USB连接,使用libusb1库。这不是一个标准的I/O代码路径,那么Python如何知道创建和使用这些连接是产生这些连接的好地方?在

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

那么asyncio下的协同程序是如何合作的呢?通过对需要与其他任务同时运行的每个逻辑代码段使用task objects,并通过使用future objects向任务发出当前逻辑代码段希望将控制权移交给其他任务的信号。这就是异步asyncio代码异步的原因,自愿放弃控制权。当循环将控制权交给多个任务中的一个任务时,该任务执行协同程序调用链的一个“步骤”,直到该调用链生成一个未来对象,此时该任务将一个wakeup回调添加到未来对象“done”回调列表,并将控制权返回给循环。在某个时候,当未来是马完成后,将运行唤醒回调,任务将执行另一个协同程序调用链步骤。在

其他一些东西负责将未来对象标记为已完成。当您使用asyncio.sleep()时,将给循环一个在特定时间运行的回调,该回调将把asyncio.sleep()未来标记为完成。当您使用stream object执行I/O时,然后(在UNIX上),循环使用^{} calls来检测何时该在I/O操作完成时唤醒未来对象。当使用lock or other synchronisation primitive时,同步原语将维护一堆期货,以便在适当的时候标记为“完成”(等待锁?给这堆东西添上一个未来。释放一把锁?从堆中选择下一个未来,并将其标记为已完成,以便等待锁的下一个任务可以唤醒并获得锁,等等)。在

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

最后但并非最不重要的是,使用asyncio的全部要点是尽量避免使用线程。使用线程有缺点;代码需要是线程安全的(控制可以在线程之间切换,所以两个线程访问共享数据块时应该小心,“小心”可能意味着代码的速度减慢了)。线程执行,不管它们是否有任何事情要做;在固定数量的线程之间切换控制是在浪费CPU时间,asyncio循环可以自由地找到一个没有等待的任务。在

So aiohttp is based on asyncio?

是的,它建立在asyncio的抽象之上,比如futurestransports and protocolssynchronization primitives,等等。在

Why doesn't asyncio offer non-blocking code with only coroutines then?

如果你使用asyncioapi,它就是这么做的。它为connect to a serverresolve a host namecreate a server、甚至run blocking code提供非阻塞代码,而不会阻塞事件循环。在

aiohttp使用所有这些功能在asyncio之上实现一个有能力的http客户机和服务器。在

Or did aiohttp implement this new event-loop (without OS-threads) itself ?

不,aiohttp钩住了asyncio的事件循环。更准确地说,使用aiohttp的应用程序会启动异步事件循环,并将aiohttp(以及其他基于异步的库)挂入其中。在

Async/await are a language feature. Asyncio is an event-loop.

Async/await是一种语言特性,与生成器类似。Asyncio是一个使用它们的库,就像itertools一样。还有其他使用协同程序的库,例如curio和{a9}。在

相关问题 更多 >