`asyncio.sleep(delay)` 是否保证睡眠至少 `delay` 秒?

Max*_*ond 3 python python-asyncio

asyncio.sleep()\ 的阻塞表兄弟time.sleep()不能保证它会休眠所请求的时间。

\n
\n

实际的挂起时间可能小于请求的时间,因为任何捕获的信号都将在执行该信号\xe2\x80\x99s 捕获例程后终止 sleep() 。

\n
\n

asyncio.sleep()\ 的文档没有提到类似的限制。

\n

是否asyncio.sleep()能够对其休眠时间做出更有力的保证?

\n

ale*_*ame 9

我不会告诉什么asyncio 保证,但基于实现,它遵循asyncio.sleep()(基本上call_later())在指定的时间间隔内休眠,但至少其不准确度等于实现中使用的系统时钟的分辨率。

让我们弄清楚一下。首先,asyncio使用单调时钟,它在不同平台上有不同的分辨率(Python 和操作系统分辨率)。例如,因为Windows这与 一样多15ms

在保证方面,请注意该函数的注释BaseEventLoop.time

    def time(self):
        """Return the time according to the event loop's clock.
        This is a float expressed in seconds since an epoch, but the
        epoch, precision, accuracy and drift are unspecified and may
        differ per event loop.
        """
        return time.monotonic()
Run Code Online (Sandbox Code Playgroud)

现在让我们看一下负责启动预定定时器的asyncio事件循环源代码:

        # Handle 'later' callbacks that are ready.
        end_time = self.time() + self._clock_resolution
        while self._scheduled:
            handle = self._scheduled[0]
            if handle._when >= end_time:
                break
            handle = heapq.heappop(self._scheduled)
            handle._scheduled = False
            self._ready.append(handle)
Run Code Online (Sandbox Code Playgroud)

线条end_time = self.time() + self._clock_resolution显示回调可能会比计划更早触发,但在时钟分辨率内。Yuri Selivanov在这里明确指出了这一点:

在我看来,目前我们正在展望未来。我们为什么不这样做

       end_time = self.time() - self._clock_resolution
Run Code Online (Sandbox Code Playgroud)

保证超时总是在请求的时间之后而不是之前触发?我不认为如果我们这样做,性能会变得更差。

实际上,让我们运行下一个程序(Windows 10 上的 Python 3.8):

import asyncio 
import time

async def main():
    print("Timer resolution", time.get_clock_info('monotonic').resolution)
    while True:
        asyncio.create_task(asyncio.sleep(1))

        t0 = time.monotonic()
        await asyncio.sleep(0.1)
        t1 = time.monotonic()

        print(t1 - t0)
asyncio.run(main()) 
Run Code Online (Sandbox Code Playgroud)

我们看到上面描述的行为:

Timer resolution 0.015625
0.09299999987706542
0.0940000000409782
0.0940000000409782
0.10900000017136335
...
Run Code Online (Sandbox Code Playgroud)

但在本文的开头,我至少说了时钟分辨率,因为asyncio它适用于协作多任务处理的条件,并且如果有一个贪婪的协程(或许多不那么贪婪的协程)不会太频繁地控制事件循环,我们有下面的图片:

import asyncio 
import time

async def calc():
    while True:
        k = 0
        for i in range(1*10**6):        
            k += i
        await asyncio.sleep(0.1)  # yield to event loop
    

async def main():
    asyncio.create_task(calc())  # start greedy coroutine
    print("Timer resolution", time.get_clock_info('monotonic').resolution)
    while True:
        asyncio.create_task(asyncio.sleep(1))

        t0 = time.monotonic()
        await asyncio.sleep(0.1)
        t1 = time.monotonic()

        print(t1 - t0)
asyncio.run(main()) 
Run Code Online (Sandbox Code Playgroud)

不出所料,情况正在发生变化,导致延迟增加:

0.17200000025331974
0.1559999999590218
0.14100000029429793
0.2190000000409782
Run Code Online (Sandbox Code Playgroud)