прерываемый сон () в Python
мне нужно sleep()
метод, который может быть прервано (как описано здесь или здесь).
мой подход заключается в том, чтобы threading.Event.wait()
тайм-аут при заданной продолжительности:
def abortable_sleep(secs, abort_event):
abort_event.wait(timeout=secs)
abort_event.clear()
после вызова abortable_sleep(10, _abort)
теперь я могу (из другого потока) называют _event.set(_abort)
пусть abortable_sleep()
завершить до 10 секунд.
пример:
def sleeping_thread():
_start = time.perf_counter()
print("%f thread started" % (time.perf_counter() - _start))
abortable_sleep(5, _abort)
print("%f thread stopped" % (time.perf_counter() - _start))
if __name__ == '__main__':
_abort = threading.Event()
while True:
threading.Thread(target=sleeping_thread).start()
time.sleep(3)
_abort.set()
time.sleep(1)
выход:
0.000001 thread started
3.002668 thread stopped
0.000002 thread started
3.003014 thread stopped
0.000001 thread started
3.002928 thread stopped
0.000001 thread started
этот код работает, как ожидалось, но я еще есть несколько вопросов:
- нет легче способ иметь s.th. likea
sleep()
, который может быть прерван? - можно ли это сделать более элегантных? Е. Г. таким образом я должен быть осторожным с
Event
экземпляр, который не привязан к экземпляруabortable_sleep()
- должен ли я ожидать проблемы с производительностью с высокочастотными петлями как
while True: abortable_sleep(0.0001)
? Как wait () - тайм-аут реализовано?
3 ответов
у меня есть класс обертки, который в основном хлопает по некоторой семантике сна поверх Event
. Хорошая вещь заключается в том, что вы только должны пройти вокруг Sleep
"объект", который можно назвать sleep()
на несколько раз, если вам нравится (sleep()
не является потокобезопасным, хотя) и что вы можете wake()
из другого потока.
from threading import Event
class Sleep(object):
def __init__(self, seconds, immediate=True):
self.seconds = seconds
self.event = Event()
if immediate:
self.sleep()
def sleep(self, seconds=None):
if seconds is None:
seconds = self.seconds
self.event.clear()
self.event.wait(timeout=seconds)
def wake(self):
self.event.set()
пример использования:
if __name__ == '__main__':
from threading import Thread
import time
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(created)d - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.info("sleep")
s = Sleep(3)
logger.info("awake")
def wake_it(sleeper):
time.sleep(1)
logger.info("wakeup!")
sleeper.wake()
logger.info("sleeping again")
s = Sleep(60, immediate=False)
Thread(target=wake_it, args=[s]).start()
s.sleep()
logger.info("awake again")
выше может вывести что-то вроде этого:
1423750549 - sleep
1423750552 - awake
1423750552 - sleeping again
1423750553 - wakeup!
1423750553 - awake again
именно то, что вы сделали, но инкапсулированы в класс.
Я бы завернул функцию sleep/abort в новый класс:
class AbortableSleep():
def __init__(self):
self._condition = threading.Condition()
def __call__(self, secs):
with self._condition:
self._aborted = False
self._condition.wait(timeout=secs)
return not self._aborted
def abort(self):
with self._condition:
self._condition.notify()
self._aborted = True
Я бы тогда также поставил Thread
подкласс для управления общим доступом к процедуре пробуждения на основе каждого потока:
class ThreadWithWakeup(threading.Thread):
def __init__(self, *args, **kwargs):
self.abortable_sleep = AbortableSleep()
super(ThreadWithWakeup, self).__init__(*args, **kwargs)
def wakeup(self):
self.abortable_sleep.abort()
любой другой поток с доступом к этой теме может вызвать wakeup()
прервать текущий abortable_sleep()
(если идет).
Использование ThreadWithWakeup
вы можете создавать темы с помощью ThreadWithWakeup
class, и используйте его как это:
class MyThread(ThreadWithWakeup):
def run(self):
print "Sleeper: sleeping for 10"
if self.abortable_sleep(10):
print "Sleeper: awoke naturally"
else:
print "Sleeper: rudely awoken"
t = MyThread()
t.start()
print "Main: sleeping for 5"
for i in range(5):
time.sleep(1)
print i + 1
print "Main: waking thread"
t.wakeup()
вывод которого выглядит так:
Sleeper: sleeping for 10
Main: sleeping for 5
1
2
3
4
5
Main: waking thread
Sleeper: rudely awoken
использование AbortableSleep самостоятельно
вы также можете использовать AbortableSleep
класс сам по себе, что удобно, если вы не можете использовать ThreadWithWakeup
класс по какой-то причине (возможно, вы находитесь в основном потоке, возможно, что-то еще создает потоки для вас и т. д.):
abortable_sleep = AbortableSleep()
def run():
print "Sleeper: sleeping for 10"
if abortable_sleep(10):
print "Sleeper: awoke naturally"
else:
print "Sleeper: rudely awoken"
threading.Thread(target=run).start()
print "Main: sleeping for 5"
for i in range(5):
time.sleep(1)
print i + 1
print "Main: aborting"
abortable_sleep.abort()
из-за условий гонки, ваше решение не всегда совершенно правильно. Вы должны использовать threading.BoundedSemaphore()
вместо. Звоните aquire()
сразу после его создания. Когда вы хотите спать, позвоните acquire()
с таймаутом, затем вызовите release()
Если acquire()
возвращено значение true. Чтобы прервать сон пораньше, звоните release()
из другой нити; это поднимет ValueError
если нет никакого сна в процессе.
использование события вместо этого проблематично, если другой поток вызывает set()
в неправильное время (т. е. в любое время, кроме того, когда вы на самом деле ждете события).