在Python中精确定时休眠

8

我需要在一个函数中等待大约25毫秒。有时候当处理器正在处理其他任务时,这个函数会被调用,另一些情况下则独占整个处理器。

我尝试使用time.sleep(.25),但有时候它实际上只是25毫秒,而其他时候则需要更长时间。是否有一种方法可以精确地睡眠一段时间,而不考虑处理器的可用性?


1
不,在非实时系统中不行。 - Dmitry B.
3
"约"25毫秒"… sleep非常适合完成这项任务。 - Karoly Horvath
7个回答

16

因为你正在使用一种抢占式操作系统,所以无法保证你的进程能够在25毫秒内控制CPU。

如果你仍想尝试,最好使用一个忙循环来轮询直到25毫秒的时间已过。类似这样的代码可能有效:

import time
target_time = time.clock() + 0.025
while time.clock() < target_time:
    pass

1
为什么忙等待在这里更好?这不取决于分配给每个进程的时间片长度吗?如果标准的时间片长度为25毫秒,忙等待肯定会更糟,因为如果活动进程数大于核心数,它几乎可以保证在等待后处理器将不可用。 - Sven Marnach
1
根据我的理解,如果你使用time.sleep暂停25毫秒,线程将被从活动队列中取出,直到过去了25毫秒,然后它将被移回到活动队列,并在未来的某个时间点执行。如果你使用忙等待,你会一直保持在活动队列上。 - Sam Mussmann
如果你需要每0.025秒重复等待并执行某些操作,你可以像这样更新target_timetarget_time += 0.025,而不是target_time = time.clock() + 0.025。这样,你就不会在等待之间“浪费”时间了。 - Lay González
在Python 3.8及更高版本中,time.clock()已被替换为time.perf_counter() - Dogkiller87

6

0.25秒等于250毫秒,不是25毫秒。除此之外,在普通操作系统上没有办法精确地等待仅仅 25 毫秒 – 你需要一些实时操作系统。


6

编辑:在Windows 10中,这种鬼扯似乎是不必要的。试试这样做:

>>> from time import sleep
>>> import timeit
>>> '%.2f%% overhead' % (timeit.timeit('sleep(0.025)', number=100, globals=globals()) / 0.025 - 100)
'0.29% overhead'

大约29%或如此的数字,相当低的开销,通常足够准确。

之前的Windows版本默认的睡眠分辨率为55毫秒,这意味着您的睡眠调用需要花费25到55毫秒。要将睡眠分辨率降至1毫秒,您需要通过调用timeBeginPeriod设置Windows使用的分辨率:

import ctypes
winmm = ctypes.WinDLL('winmm')
winmm.timeBeginPeriod(1)

5

另一个准确计时和延迟的解决方案是使用time模块中的perf_counter()函数。在Windows操作系统中特别有用,因为time.sleep在毫秒级别上不够精确。请参见下面的示例,其中accurate_delay函数创建了毫秒级别的延迟。

import time


def accurate_delay(delay):
    ''' Function to provide accurate time delay in millisecond
    '''
    _ = time.perf_counter() + delay/1000
    while time.perf_counter() < _:
        pass


delay = 10
t_start = time.perf_counter()
print('Wait for {:.0f} ms. Start: {:.5f}'.format(delay, t_start))

accurate_delay(delay)

t_end = time.perf_counter()
print('End time: {:.5f}. Delay is {:.5f} ms'.
      format(t_end, 1000*(t_end - t_start)))

sum = 0
ntests = 1000
for _ in range(ntests):
    t_start = time.perf_counter()
    accurate_delay(delay)
    t_end = time.perf_counter()
    print('Test completed: {:.2f}%'.format(_/ntests * 100), end='\r', flush=True)
    sum = sum + 1000*(t_end - t_start) - delay

print('Average difference in time delay is {:.5f} ms.'.format(sum/ntests))

5

您所使用的操作系统是什么?如果您使用的是Windows,您可能希望按照以下方式进行精确计时:

import ctypes
kernel32 = ctypes.windll.kernel32

# This sets the priority of the process to realtime--the same priority as the mouse pointer.
kernel32.SetThreadPriority(kernel32.GetCurrentThread(), 31)
# This creates a timer. This only needs to be done once.
timer = kernel32.CreateWaitableTimerA(ctypes.c_void_p(), True, ctypes.c_void_p())
# The kernel measures in 100 nanosecond intervals, so we must multiply .25 by 10000
delay = ctypes.c_longlong(.25 * 10000)
kernel32.SetWaitableTimer(timer, ctypes.byref(delay), 0, ctypes.c_void_p(), ctypes.c_void_p(), False)
kernel32.WaitForSingleObject(timer, 0xffffffff)

这段代码将几乎保证您的进程将会休眠0.25秒。但要注意,除非绝对必要,否则您可能需要将优先级降低到2或3。对于用户端产品,千万不要将优先级设置得太高。


非常老的答案,我知道...但是你有没有可能知道如何在Windows10 + Python3上使其工作? - Brian McFarland

2
你想要做的是实时应用程序。Python(以及你可能使用的操作系统)不适用于编写这种时间限制非常严格的应用程序。
为了达到你的目标,你需要一个实时操作系统(RTOS),并使用适合的编程语言(通常是C语言)遵循实时最佳实践开发你的应用程序。

1

文档的sleep方法中:

暂停执行给定的秒数。参数可以是浮点数,以指示更精确的睡眠时间。实际的暂停时间可能会比请求的时间少,因为任何捕获的信号都将在执行该信号的捕获例程后终止sleep()。此外,由于系统中其他活动的调度,暂停时间可能会比请求的时间长。

事实上,这取决于您的底层操作系统。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接