“asyncio.sleep(Delay)”是否保证睡眠至少需要“delay”秒?

2022-03-25 00:00:00 python python-asyncio

问题描述

asyncio.sleep()的阻塞表亲time.sleep()无法保证它将休眠请求的时间。

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

asyncio.sleep()%sdocumentation没有提到类似的限制。

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


解决方案

我不会说asyncio保证,但根据实现可以得出结论:asyncio.sleep()(基本上是call_later())在指定的时间间隔内休眠,但至少不精确到实现中使用的系统时钟的分辨率。

让我们把它弄清楚。首先,asyncio使用单调时钟,其分辨率on different platforms不同(Python和OS分辨率)。例如,对于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()

现在让我们看一下负责启动计划计时器的asyncio事件循环源code:

        # 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)

end_time = self.time() + self._clock_resolutionend_time = self.time() + self._clock_resolution显示回调可能比计划提前,但在时钟分辨率范围内。尤里·谢利万诺夫对此明确表示here:

在我看来,目前我们可以窥视未来的时间。我们为什么不这样做

       end_time = self.time() - self._clock_resolution

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

实际上,让我们运行下一个程序(Windows10上的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()) 

我们看到上述行为:

Timer resolution 0.015625
0.09299999987706542
0.0940000000409782
0.0940000000409782
0.10900000017136335
...

但在正文的开头,我说了至少是时钟分辨率,因为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()) 

情况不出所料地朝着增加延迟的方向变化:

0.17200000025331974
0.1559999999590218
0.14100000029429793
0.2190000000409782

相关文章