Python 为什么asyncio.sleep(0)使我的代码更快?

Python 为什么asyncio.sleep(0)使我的代码更快?,python,python-asyncio,Python,Python Asyncio,如果我从扫描仪中删除这行 await asyncio.sleep(0) 工作时间从5秒增加到400秒 为什么会这样 我的代码: import os import asyncio import time async def rl(response): await asyncio.sleep(0) return response.readlines() async def scan_Ip(addr): print(addr) response = os.po

如果我从扫描仪中删除这行

await asyncio.sleep(0)
工作时间从5秒增加到400秒

为什么会这样

我的代码:

import os
import asyncio
import time

async def rl(response):
    await asyncio.sleep(0)
    return response.readlines()  

async def scan_Ip(addr):
    print(addr)
    response = os.popen("ping -n 1 " + addr)
    data = await rl(response)
    for line in data:
        if 'TTL' in line:
            print(data)

async def scan():
    tasks=[]
    for ip in range(0, 256):
        tasks.append(asyncio.create_task(scan_Ip(f'192.168.8.{ip}')))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    start_time = time.time()
    asyncio.run(scan())
    print(f"--- {time.time() - start_time} seconds ---")

在运行代码之后,它实际上根本不会增加我机器上的运行时间(或者只增加最少的次数)。我假设这会以某种方式触发类似于速率限制(非常短)的事件,并且有时必须重试,从而有效地将运行时间加倍。你的机器可能不同,但这是我在MacOS Catalina上得到的

使用
异步睡眠(0)
:1.9657979011535645秒


如果没有
asyncio.sleep(0)
:运行代码后3.2927019596099854秒

,它实际上根本不会增加我的计算机上的运行时间(或者只增加最短时间)。我假设这会以某种方式触发类似于速率限制(非常短)的事件,并且有时必须重试,从而有效地将运行时间加倍。你的机器可能不同,但这是我在MacOS Catalina上得到的

使用
异步睡眠(0)
:1.9657979011535645秒


如果没有
asyncio.sleep(0)
:3.2927019596099854秒

正如Nullman在一篇关于这个问题的评论中指出的那样,
os.popen
不提供异步接口,因此从asyncio使用它会使执行此操作的协程阻塞事件循环。这样做的结果是,无论使用
gather
wait
如何,多个这样的协同程序都会顺序执行

请注意,您的
rl
函数如何通过阻塞
readlines()
调用读取数据,而实际上并不等待任何操作(除了
sleep(0)
)。这是一个可靠的指标,表明除名称外,
rl
不是异步的,并且一旦您尝试并行运行它,它将导致瓶颈

从asyncio与
ping
接口的正确方法是使用与
os.popen
等效的asyncio:

async def scan_Ip(addr):
    print(addr)
    proc = await asyncio.create_subprocess_exec(
        "ping", "-n", "1", addr, stdout=subprocess.PIPE
    )
    async for line in proc.stdout:
        if 'TTL' in line:
            print(data)
现在通过异步调用执行进程交互,这允许对所有256个进程并行执行它们

至于为什么
等待asyncio.sleep(0)
帮助-等待
asyncio.sleep()
强制异步函数将控制权交给事件循环()。这允许另一个协同程序执行后续的
等待
,以此类推,并最终以对该程序有益的方式影响执行顺序。如果没有
asyncio.sleep(0)
scan\u Ip/
rl
对,则不会落到事件循环中,并按顺序执行,如下所示(简化):

换句话说,在考虑另一个之前,每个
ping
都会启动、完全处理和停止。在
os.popen()
readlines()
之间使用
wait asyncio.sleep(0)
,代码还不是完全异步的,但它确实在这两个步骤之间添加了一个开关,导致以下执行顺序:

  1. x1 = os.popen("ping -n 1 192.168.8.0")
  2. x2 = os.popen("ping -n 1 192.168.8.1")
...
256. x256 = os.popen("ping -n 1 192.168.8.255")
257. for line in x1.readlines(): ...
258. for line in x2.readlines(): ...
...
512. for line in x256.readlines(): ...
换句话说,执行相同的步骤,但重新排序,以便首先启动所有命令,然后才从中读取命令。这允许
ping
命令并行生成其输出(并连接到网络等)。然后,通信步骤只拾取基本就绪的输出,该输出位于管道缓冲区中。所有
ping
s并行执行的事实使该版本更快


最后,请注意,惯用的异步IO代码,作为使用
asyncio.subprocess
的代码,不需要
asyncio.sleep(0)
,因为它在等待数据时已经一直在等待,而强制额外的睡眠实际上会使它变慢。

正如Nullman在对该问题的评论中指出的那样,
os.popen
不提供异步接口,因此从asyncio使用它会使执行此操作的协程阻塞事件循环。这样做的结果是,无论使用
gather
wait
如何,多个这样的协同程序都会顺序执行

请注意,您的
rl
函数如何通过阻塞
readlines()
调用读取数据,而实际上并不等待任何操作(除了
sleep(0)
)。这是一个可靠的指标,表明除名称外,
rl
不是异步的,并且一旦您尝试并行运行它,它将导致瓶颈

从asyncio与
ping
接口的正确方法是使用与
os.popen
等效的asyncio:

async def scan_Ip(addr):
    print(addr)
    proc = await asyncio.create_subprocess_exec(
        "ping", "-n", "1", addr, stdout=subprocess.PIPE
    )
    async for line in proc.stdout:
        if 'TTL' in line:
            print(data)
现在通过异步调用执行进程交互,这允许对所有256个进程并行执行它们

至于为什么
等待asyncio.sleep(0)
帮助-等待
asyncio.sleep()
强制异步函数将控制权交给事件循环()。这允许另一个协同程序执行后续的
等待
,以此类推,并最终以对该程序有益的方式影响执行顺序。如果没有
asyncio.sleep(0)
scan\u Ip/
rl
对,则不会落到事件循环中,并按顺序执行,如下所示(简化):

换句话说,在考虑另一个之前,每个
ping
都会启动、完全处理和停止。在
os.popen()
readlines()
之间使用
wait asyncio.sleep(0)
,代码还不是完全异步的,但它确实在这两个步骤之间添加了一个开关,导致以下执行顺序:

  1. x1 = os.popen("ping -n 1 192.168.8.0")
  2. x2 = os.popen("ping -n 1 192.168.8.1")
...
256. x256 = os.popen("ping -n 1 192.168.8.255")
257. for line in x1.readlines(): ...
258. for line in x2.readlines(): ...
...
512. for line in x256.readlines(): ...
换句话说,执行相同的步骤,但重新排序,以便首先启动所有命令,然后才从中读取命令。这允许
ping
命令并行生成其输出(并连接到网络等)。然后,通信步骤只拾取基本就绪的输出,该输出位于管道缓冲区中。所有
ping
s并行执行的事实使该版本更快

最后,请注意惯用的异步IO代码<