прерываемый сон () в 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() в неправильное время (т. е. в любое время, кроме того, когда вы на самом деле ждете события).