Проверка времени в цикле, вероятно, здесь не нужна и расточительна, так как вы можете уложить поток в спящий режим и позволить ядру разбудить его, если пришло время.Библиотека потоков обеспечивает threading.Timer
для таких случаев использования.Трудность в вашем случае заключается в том, что вы не можете прервать такой спящий поток, чтобы настроить интервал, после которого должна выполняться указанная функция.
В моем примере ниже я использую пользовательский класс менеджера TimeLord
, чтобы преодолеть это ограничение.TimeLord
позволяет «сбросить» таймер, отменив текущий таймер и заменив его новым.Для этой цели TimeLord
содержит промежуточную промежуточную рабочую функцию и атрибут token, который должен быть запущен запущенным экземпляром таймера для выполнения указанной целевой функции.
Этот дизайн гарантирует уникальное выполнение указанной целевой функции, поскольку dict.pop()
является атомарной операцией.timelord.reset()
действует до тех пор, пока текущий таймер не запустил свой поток и не вытолкнул _token
.Этот подход не может полностью предотвратить потенциально неэффективные запуски новых потоков таймера при попытке «перезагрузки», но это некритическая избыточность, когда это происходит, поскольку целевая функция может быть выполнена только один раз.
Этот код работает с Python2 и 3:
import time
from datetime import datetime
from threading import Timer, current_thread
def f(x):
print('{} {}: RUNNING TARGET FUNCTION'.format(
datetime.now(), current_thread().name)
)
time.sleep(x)
print('{} {}: EXITING'.format(datetime.now(), current_thread().name))
class TimeLord:
"""
Manager Class for threading.Timer instance. Allows "resetting" `interval`
as long execution of `function` has not started by canceling the old
and constructing a new timer instance.
"""
def worker(self, *args, **kwargs):
try:
self.__dict__.pop("_token") # dict.pop() is atomic
except KeyError:
pass
else:
self.func(*args, **kwargs)
def __init__(self, interval, function, args=None, kwargs=None):
self.func = function
self.args = args if args is not None else []
self.kwargs = kwargs if kwargs is not None else {}
self._token = True
self._init_timer(interval)
def _init_timer(self, interval):
self._timer = Timer(interval, self.worker, self.args, self.kwargs)
self._timer.daemon = True
def start(self):
self._timer.start()
print('{} {}: STARTED with `interval={}`'.format(
datetime.now(), self._timer.name, self._timer.interval)
)
def reset(self, interval):
"""Cancel latest timer and start a new one if `_token` is still there.
"""
print('{} {}: CANCELED'.format(datetime.now(), self._timer.name))
self._timer.cancel()
# reduces, but doesn't prevent, occurrences when a new timer
# gets created which eventually will not succeed in popping
# the `_token`. That's uncritical redundancy when it happens.
# Only one thread ever will be able to execute `self.func()`
if hasattr(self, "_token"):
self._init_timer(interval)
self.start()
def cancel(self):
self._timer.cancel()
def join(self, timeout=None):
self._timer.join(timeout=timeout)
def run_demo(initial_interval):
print("*** testing with initial interval {} ***".format(initial_interval))
tl = TimeLord(interval=initial_interval, function=f, args=(10,))
tl.start()
print('*** {} sleeping two seconds ***'.format(datetime.now()))
time.sleep(2)
tl.reset(interval=6)
tl.reset(interval=7)
tl.join()
print("-" * 70)
if __name__ == '__main__':
run_demo(initial_interval=5)
run_demo(initial_interval=2)
Пример вывода:
*** testing with initial interval 5 ***
2019-06-05 20:58:23.448404 Thread-1: STARTED with `interval=5`
*** 2019-06-05 20:58:23.448428 sleeping two seconds ***
2019-06-05 20:58:25.450483 Thread-1: CANCELED
2019-06-05 20:58:25.450899 Thread-2: STARTED with `interval=6`
2019-06-05 20:58:25.450955 Thread-2: CANCELED
2019-06-05 20:58:25.451496 Thread-3: STARTED with `interval=7`
2019-06-05 20:58:32.451592 Thread-3: RUNNING TARGET FUNCTION
2019-06-05 20:58:42.457527 Thread-3: EXITING
----------------------------------------------------------------------
*** testing with initial interval 2 ***
2019-06-05 20:58:42.457986 Thread-4: STARTED with `interval=2`
*** 2019-06-05 20:58:42.458033 sleeping two seconds ***
2019-06-05 20:58:44.458058 Thread-4: RUNNING TARGET FUNCTION
2019-06-05 20:58:44.459649 Thread-4: CANCELED
2019-06-05 20:58:44.459724 Thread-4: CANCELED
2019-06-05 20:58:54.466342 Thread-4: EXITING
----------------------------------------------------------------------
Process finished with exit code 0
Обратите внимание, что с интервалом = 2 отмены через две секунды не имели никакого эффекта, так как таймер былуже выполняет целевую функцию.