LINUX.ORG.RU

[python] Interruptable timer


0

1

Задача: необходимо в отдельном потоке периодически запускать функцию, при этом нужно иметь возможность как можно быстрее завершить поток.

У меня было хорошее решение с использованием threading.Event, но как выяснилось у него слишком прожорливое ожидание таймаута. Поэтому пришел к такому костылю:

import threading
from select import select
import os
import time

class Timer(threading.Thread):
    def __init__(self, interval, target, *args, **kwargs):
        threading.Thread.__init__(self)
        self.interval = interval
        self.target = target
        self.args = args
        self.kwargs = kwargs

        self.rfd = self.wfd = None
        self.cancel_requested = False

    def run(self):
        self.rfd, self.wfd = os.pipe()
        while True:
            readable, _, exc = select([self.rfd], [], [], self.interval)

            if self.cancel_requested:
                break

            self.target(*self.args, **self.kwargs)

    def cancel(self, block=True):
        self.cancel_requested = True
        os.write(self.wfd, '1')
        if block:
            self.join()

        os.close(self.rfd)
        os.close(self.wfd)


def target(t):
    print t[0]
    t[0] += 1

t = Timer(2.0, target, [1])
t.start()

time.sleep(10.5)
t.cancel()

print 'done'

Может есть более элегантные способы добиться цели?

Ответ на: комментарий от baverman

> Ого, оказывается у моего костыля есть название.

timerfd и eventfd - это нормальные системные механизмы, а костыль - это у тебя. Правда, это годный, классический костыль.

tailgunner ★★★★★ ()
Ответ на: комментарий от true_admin

почему timerfd это костыль?

Да нет, я имел в виду, что идея ожидать интервал через select имеет свое воплощение.

Единственная проблема: «These system calls are Linux-specific».

baverman ★★★ ()
Ответ на: комментарий от baverman

> Единственная проблема: «These system calls are Linux-specific».

В рамках посикса - timer_create и ждать сигнал в pselect. Линуксовые timerfd и signalfd позволяют проще и аккуратнее организовать логику программы.

const86 ★★★★★ ()

> У меня было хорошее решение с использованием threading.Event, но как выяснилось у него слишком прожорливое ожидание таймаута.

А это как?

tailgunner ★★★★★ ()
Ответ на: комментарий от baverman

идея ожидать интервал через select имеет свое воплощение.

И это правильно! Это позволяет делать единый event-loop. Поэтому я очень рад что сокеты, таймеры, inotify/fanotify, итд итп поддерживают поллинг.

Единственная проблема: «These system calls are Linux-specific».

нифига, во фряхе тоже есть. На счёт других осей не знаю, но, например, на солярку и винду я бы забил. А всякие qnx... тоже не стал бы париться. Что ещё я забыл? :)

true_admin ★★★★★ ()
Ответ на: комментарий от baverman

Трогать ctypes не хочу.

why? Оно же отлично работает и это как раз тот случай когда оно уместно. Я уже научился обходиться без сегфолтов :)

true_admin ★★★★★ ()
Ответ на: комментарий от ntp

threading.Timer

У него жручее ожидание, сделанное через threading.Event.

baverman ★★★ ()
Ответ на: комментарий от const86

Дык тогда twisted же

Глупо. Из-за тридцати строчек костылей тянуть в зависимости этого монстра.

baverman ★★★ ()
Ответ на: комментарий от tailgunner

Да, пожалуй.

import threading
import time

def target(is_stop, interval):
    i = 1
    while not is_stop.wait(interval):
        print i
        i += 1

is_stop = threading.Event()
t = threading.Thread(target=target, args=(is_stop, 2.0))
t.start()

time.sleep(10.5)
is_stop.set()
t.join()

print 'done'
baverman ★★★ ()
Ответ на: комментарий от baverman

Не знаю, что там у тебя за Питон, но у меня твой фрагмент тупо не работает - нить не завершается, если после «i += 1» нет примерно такого:

        if is_stop.isSet():
            break
tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Понятно.

Event.wait([timeout])

Changed in version 2.7: Previously, the method always returned None.

Да уж. Не лучший вариант для программы, которая должна работать и на 2.5.

baverman ★★★ ()
Ответ на: комментарий от baverman

По-моему, что бы оно не возвращало, всё равно нужно проверять isSet. Иначе - плотный цикл, не самый эффективный метод ожидания.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Иначе - плотный цикл

Где? Как только флаг поднят — сразу выход. А wait блокируется (правда там внутрях не все прекрасно и минимум двадцать раз в секунду проверяется лок).

baverman ★★★ ()
Ответ на: комментарий от baverman

> минимум двадцать раз в секунду проверяется лок

Можно сделать тупо sleep и проверять лок, скажем, 5 раз в секунду (с соотвествующим уменьшением реактивности). Похоже, что переносимыми средствами Питона можно сделать либо точную реакцию, либо быстрое завершение.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Меня select вполне удовлетворил. Два лишних файловых дескриптора на поток — небольшая цена при python-only решении. На этом и остановлюсь.

baverman ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.