异步:休眠亚毫秒间隔

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

问题描述

我正在构建一个基于覆盆子PI的设备。它将有几个应该同时工作的并发功能。在这种情况下,使用Asyncio看起来是一个合理的选择(嗯,我可以用C++用线程编写所有这些东西,但是Python代码看起来要紧凑得多)

功能之一是通过GPIO脉冲驱动步进电机。这些脉冲应为5-10微秒长。有没有办法在异步睡眠的情况下在亚毫秒间隔内入睡?


解决方案

有没有办法使用异步睡眠在亚毫秒间隔内入睡?

在Linux asyncio上,异步使用epoll_wait系统调用,该系统调用以毫秒为单位指定超时,因此,任何以毫秒为单位的内容都不会起作用,尽管可以在asyncio.sleep()中指定。

您可以通过运行以下程序在您的计算机上测试它:

import asyncio, os

SLEEP_DURATION = 5e-3  # 5 ms sleep

async def main():
    while True:
        # suspend execution
        await asyncio.sleep(SLEEP_DURATION)
        # execute a syscall visible in strace output
        os.stat('/tmp')

asyncio.run(main())

将程序另存为sleep1.py,然后在strace下运行,如下所示:

$ strace -fo trc -T python3.7 sleep1.py
<wait a second or two, then press Ctrl-C to interrupt>

trc文件将包含相当精确的幕后运行时间。在Python启动序列之后,程序基本上在无限循环中执行以下操作:

24015 getpid()                          = 24015 <0.000010>
24015 epoll_wait(3, [], 1, 5)           = 0 <0.005071>
24015 epoll_wait(3, [], 1, 0)           = 0 <0.000010>
24015 stat("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=45056, ...}) = 0 <0.000014>

我们看到一个对getpid()的调用,两个对epoll_wait的调用,最后是对stat的调用。第一个epoll_wait实际上是相关的,它以毫秒为单位指定超时,并休眠大约所需的时间段。如果我们将睡眠持续时间降低到亚毫秒级,例如100e-6,strace显示Asyncio仍然向epoll_wait请求1ms超时,并获得同样多的超时。同样的情况也会发生在暂停到15秒的时候。如果您指定14 us或更小超时,则Asyncio实际上请求无超时轮询,epoll_wait在8 us内完成。但是,第二个epoll_wait也需要8微秒,所以您不能指望任何形式的微秒分辨率。

即使您使用线程和繁忙循环,也很可能会遇到GIL的同步问题。这很可能是在低级语言(如C++或Rust)中完成的,即使如此,您也需要小心操作系统调度程序。

相关文章