首先,我是Python的新手,并不熟悉它的功能。我一直主要使用MATLAB。
PC简要规范:Windows 10,Intel i7
我正在尝试创建一个定时器类来定期执行MATLAB所具有的功能,这显然是从Java定时器中借用的。 MATLAB定时器的分辨率约为1 ms,在任何情况下我都没有看到它超过2 ms。事实上,它对我的项目来说足够准确。
最近,我计划转向Python,因为MATLAB的并行计算和Web访问功能很差。然而,遗憾的是,与MATLAB相比,Python的标准软件包提供了一些低级别的计时器(threading.Timer),我必须自己制作计时器类。首先,我提到了QnA Executing periodic actions in Python [duplicate]。 Michael Anderson 建议的解决方案给出了漂移校正的简单概念。他用time.sleep()来保持这段时间。该方法非常准确,有时显示出比MATLAB计时器更高的精度。约。 0.5毫秒分辨率。但是,在time.sleep()中捕获期间,定时器不能被中断(暂停或恢复)。但我有时不得不立即停止,无论是否处于睡眠状态()。
我发现问题的解决方案是在线程包中使用Event类。请参阅Python threading.timer - repeat function every 'n' seconds 。使用Event.wait()的超时功能,我可以在执行之间留出时间间隔,它用于保持周期。也就是说,事件通常被清除,以便wait(timeout)可以像time.sleep(interval)一样,我可以在需要时通过设置事件立即退出wait()。
一切似乎都很好但是在Event.wait()中存在严重问题。时间延迟在1~15 ms之间变化很大。我认为它来自Event.wait()的开销。
我做了一个示例代码,显示time.sleep()和Event.wait()之间的准确性比较。这总计1000次迭代的1 ms sleep()和wait()以查看累积的时间错误。预期结果约为1.000。
import time
from threading import Event
time.sleep(3) # to relax
# time.sleep()
tspan = 1
N = 1000
t1 = time.perf_counter()
for _ in range(N):
time.sleep(tspan/N)
t2 = time.perf_counter()
print(t2-t1)
time.sleep(3) # to relax
# Event.wait()
tspan = 1
event = Event()
t1 = time.perf_counter()
for _ in range(N):
event.wait(tspan/N)
t2 = time.perf_counter()
print(t2-t1)
结果:
1.1379848184879964
15.614547161211096
结果显示time.sleep()的准确性要好得多。但我不能完全依赖于前面提到的time.sleep()。
总之,
我目前正在考虑妥协:正如在示例中,创建一个微小的time.sleep()循环(0.5毫秒间隔)并使用if语句和 break 退出循环需要的时候。据我所知,该方法用于Python 2.x Python time.sleep() vs event.wait()。
这是一个冗长的介绍,但我的问题可归纳如下。
我可以通过外部信号或事件强制线程进程从time.sleep()中断吗? (这似乎是最有效的。???)
使Event.wait()更准确或减少开销时间。
除了sleep()和Event.wait()方法之外,还有更好的方法来提高计时精度。
非常感谢。
答案 0 :(得分:0)
我遇到了Event.wait()
的同一计时问题。我想到的解决方案是创建一个模拟threading.Event
的类。在内部,它使用time.sleep()
循环和忙碌循环的组合来大大提高精度。睡眠循环在单独的线程中运行,因此仍可以立即中断主线程中的阻塞wait()
调用。调用set()
方法时,休眠线程应在此后不久终止。另外,为了最大程度地减少CPU使用率,我确保忙碌循环永远不会运行超过3毫秒。
这是我自定义的Event
类,最后还有一个计时演示(演示中的打印执行时间将以纳秒为单位):
import time
import _thread
import datetime
class Event:
__slots__ = (
"_flag", "_lock", "_nl",
"_pc", "_waiters"
)
_lock_type = _thread.LockType
_timedelta = datetime.timedelta
_perf_counter = time.perf_counter
_new_lock = _thread.allocate_lock
class _switch:
__slots__ = ("_on",)
def __call__(self, on: bool = None):
if on is None:
return self._on
self._on = on
def __bool__(self):
return self._on
def __init__(self):
self._on = False
def clear(self):
with self._lock:
self._flag(False)
def is_set(self) -> bool:
return self._flag()
def set(self):
with self._lock:
self._flag(True)
waiters = self._waiters
for waiter in waiters:
waiter.release()
waiters.clear()
def wait(
self,
timeout: float = None
) -> bool:
with self._lock:
return self._wait(self._pc(), timeout)
def _new_waiter(self) -> _lock_type:
waiter = self._nl()
waiter.acquire()
self._waiters.append(waiter)
return waiter
def _wait(
self,
start: float,
timeout: float,
td=_timedelta,
pc=_perf_counter,
end: _timedelta = None,
waiter: _lock_type = None,
new_thread=_thread.start_new_thread,
thread_delay=_timedelta(milliseconds=3)
) -> bool:
flag = self._flag
if flag:
return True
elif timeout is None:
waiter = self._new_waiter()
elif timeout <= 0:
return False
else:
delay = td(seconds=timeout)
end = td(seconds=start) + delay
if delay > thread_delay:
mark = end - thread_delay
waiter = self._new_waiter()
new_thread(
self._wait_thread,
(flag, mark, waiter)
)
lock = self._lock
lock.release()
try:
if waiter:
waiter.acquire()
if end:
while (
not flag and
td(seconds=pc()) < end
):
pass
finally:
lock.acquire()
if waiter and not flag:
self._waiters.remove(waiter)
return flag()
@staticmethod
def _wait_thread(
flag: _switch,
mark: _timedelta,
waiter: _lock_type,
td=_timedelta,
pc=_perf_counter,
sleep=time.sleep
):
while not flag and td(seconds=pc()) < mark:
sleep(0.001)
if waiter.locked():
waiter.release()
def __new__(cls):
_new_lock = cls._new_lock
_self = object.__new__(cls)
_self._waiters = []
_self._nl = _new_lock
_self._lock = _new_lock()
_self._flag = cls._switch()
_self._pc = cls._perf_counter
return _self
if __name__ == "__main__":
def test_wait_time():
wait_time = datetime.timedelta(microseconds=1)
wait_time = wait_time.total_seconds()
def test(
event=Event(),
delay=wait_time,
pc=time.perf_counter
):
pc1 = pc()
event.wait(delay)
pc2 = pc()
pc1, pc2 = [
int(nbr * 1000000000)
for nbr in (pc1, pc2)
]
return pc2 - pc1
lst = [
f"{i}.\t\t{test()}"
for i in range(1, 11)
]
print("\n".join(lst))
test_wait_time()
del test_wait_time
答案 1 :(得分:0)
Chris D 的自定义 Event 类效果非常好!出于实用目的,我将它包含在一个可安装的包中(https://github.com/ovinc/oclock,使用 pip install oclock
安装),其中还包含其他计时工具。从 oclock
的 1.3.0 版开始,可以使用 Chris D 的回答中讨论的自定义 Event
类,例如
from oclock import Event
event = Event()
event.wait(1)
使用 set()
类的常用 clear()
、is_set()
、wait()
、Event
方法。
计时精度比 threading.Event
好得多,尤其是在 Windows 中。例如,在具有 1000 个重复循环的 Windows 机器上,threading.Event
的循环持续时间为 7 毫秒,oclock.Event
的标准偏差小于 0.01 毫秒。克里斯 D 的道具!
注意:oclock
软件包在 GPLv3 许可下与 StackOverflow 的 CC BY-SA 4.0 兼容。
答案 2 :(得分:0)
感谢您的主题和所有答案。我还遇到了一些计时不准确的问题(Windows 10 + Python 3.9 + 线程)。
解决方案是使用 oclock 包,并通过 wres 包(临时)更改 Windows 系统计时器的分辨率。此软件包使用未记录的 Windows API 函数 NtSetTimerResolution(警告:分辨率已在系统范围内更改)。
仅应用oclock包并不能解决问题。
应用这两个 python 包后,下面的代码可以正确且准确地安排定期事件。如果终止,则恢复原始计时器分辨率。
import threading
import datetime
import time
import oclock
import wres
class Job(threading.Thread):
def __init__(self, interval, *args, **kwargs):
threading.Thread.__init__(self)
# use oclock.Event() instead of threading.Event()
self.stopped = oclock.Event()
self.interval = interval.total_seconds()
self.args = args
self.kwargs = kwargs
def stop(self):
self.stopped.set()
self.join()
def run(self):
prevTime = time.time()
while not self.stopped.wait(self.interval):
now = time.time()
print(now - prevTime)
prevTime = now
# Set system timer resolution to 1 ms
# Automatically restore previous resolution when exit with statement
with wres.set_resolution(10000):
# Create thread with periodic task called every 10 ms
job = Job(interval=datetime.timedelta(seconds=0.010))
job.start()
try:
while True:
time.sleep(1)
# Hit Ctrl+C to terminate main loop and spawned thread
except KeyboardInterrupt:
job.stop()