lhk*_*lhk 1 python multithreading asynchronous python-asyncio aiohttp
默认情况下,asyncio同步运行协同程序.如果它们包含阻塞IO代码,它们仍会等待它返回.解决这个问题的方法是loop.run_in_executor()将代码转换为线程.如果线程在IO上阻塞,则另一个线程可以开始执行.所以你不要浪费时间等待IO调用.
如果您在asyncio没有执行者的情况下使用,那么您将失去这些加速.所以我想知道,为什么你必须明确地使用执行器.为什么不默认启用它们?(在下文中,我将重点关注http请求.但它们实际上只是作为一个例子.我对一般原则感兴趣.)
经过一番搜索,我找到了aiohttp.它是一个基本上提供asyncio和组合的库requests:非阻塞HTTP调用.使用执行程序,asyncio并且requests表现得非常像aiohttp.是否有理由实施新库,您是否因使用执行程序而支付性能损失?
回答了这个问题:为什么asyncio总是不使用执行程序?
Mikhail Gerasimov向我解释过,执行程序会启动操作系统线程并且它们会变得昂贵.因此,不将它们作为默认行为是有道理的.aiohttp比requests在执行程序中使用模块更好,因为它提供只有协同程序的非阻塞代码.
这让我想到了这个问题.aiohttp将自己宣传为:
用于asyncio和Python的异步HTTP客户端/服务器.
所以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的命名空间可以在执行期间的任何时候进行更改,所以在time.sleep()实际执行该函数之前,您无法知道将执行什么操作.
你可以说time.sleep()实现应该在被调用时自动生成,但是你必须开始识别所有这些函数.您必须修补的地方数量没有限制,您无法知道所有地方.当然不适合第三方图书馆.例如,该python-adb项目使用libusb1库为您提供与Android设备的同步USB连接.那不是标准的I/O代码路径,那么Python如何知道创建和使用这些连接是很好的产生的地方?
因此,您不能仅仅假设代码需要在执行程序中运行,并非所有代码都可以在执行程序中运行,因为它不是线程安全的,并且Python无法检测代码何时阻塞并且应该真正屈服.
那么协同程序如何asyncio合作?通过使用需要与其他任务并发运行的每个逻辑代码段的任务对象,并使用将来的对象向任务发出信号,表示当前逻辑代码段要将控制权交给其他任务.这就是使异步asyncio代码异步,自愿放弃控制的原因.当循环控制一个任务中的一个任务时,该任务执行协同调用链的单个"步骤",直到该调用链产生一个未来对象,此时该任务为未来对象添加一个唤醒回调. '回调列表并将控制权返回给循环.稍后,当未来标记完成时,将运行唤醒回调,并且任务将执行另一个协程调用链步骤.
一些别的负责标志着未来对象作为完成.当您使用时asyncio.sleep(),将在特定时间运行回调给循环,其中该回调将asyncio.sleep()未来标记为已完成.当您使用流对象执行I/O时,(在UNIX上),循环使用select调用来检测何时在I/O操作完成时唤醒未来对象的时间.当你使用锁或其他同步原语时,同步原语将保持一堆期货在适当的时候标记为"完成"(等待锁定?向堆中添加未来.释放持有的锁定?选择下一个堆的未来并将其标记为已完成,因此等待锁的下一个任务可以唤醒并获取锁等.
将阻塞的同步代码放入执行程序只是另一种形式的合作.asyncio在项目中使用时,开发人员应确保使用提供给您的工具来确保您的协同程序协作.您可以自由地open()对文件使用阻塞调用而不是使用流,并且当您知道代码需要在单独的线程中运行以避免阻塞太长时,您可以自由地使用执行程序.
最后但并非最不重要的是,使用的全部目的asyncio是尽可能避免使用线程.使用线程有缺点; 代码需要是线程安全的(控件可以在任何地方的线程之间切换,因此访问共享数据的两个线程应该小心处理,并且"小心"可能意味着代码速度变慢).无论是否有任何事情要做,线程都会执行; 在所有等待I/O发生的固定数量的线程之间切换控制是浪费CPU时间,其中asyncio循环可以自由地找到不等待的任务.