`asyncio.sleep(delay)`是否一定要睡至少`delay`秒?

3 人关注

asyncio.sleep() 's blocking cousin, time.sleep() ,不能保证它能睡到要求的时间。

实际的暂停时间可能比要求的时间要短,因为任何被捕获的信号都会在执行该信号的捕获例程后终止sleep()。

asyncio.sleep() 's 文件 并没有提到类似的限制。

替换代码0】是否能够对它的睡眠时间做出更有力的保证?

python
python-asyncio
Max Bond
Max Bond
发布于 2021-01-14
1 个回答
alex_noname
alex_noname
发布于 2021-01-15
已采纳
0 人赞同

我不会告诉你什么 asyncio 担保 但根据执行情况,可以看出 asyncio.sleep() (基本上是 call_later() )在指定的时间间隔内睡眠。 但至少要有一个与系统时钟的分辨率相等的不准确度 在执行中使用。

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

In terms of 担保, pay attention to the comment to the function 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_resolution行显示,回调可提前发射比计划的要好,但在时钟分辨率内。尤里-塞利瓦诺夫对此明确表示here:

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

       end_time = self.time() - self._clock_resolution

以保证超时总是被触发的之后的时间,而不是之前? 我不明白如果我们这样做,性能怎么会变差。

真的,我们来运行下一个程序(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()) 

我们看到上述的行为。

Timer resolution 0.015625
0.09299999987706542
0.0940000000409782
0.0940000000409782
0.10900000017136335

但在正文的开头,我说至少是时钟分辨率,因为asyncio在合作多任务的条件下起作用,如果有一个贪婪的coroutine(或许多不那么贪婪的coroutine),不会太频繁地把控制权交给事件循环,我们就会有以下情况。

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

毫不奇怪,情况正朝着增加延迟的方向变化。