правильный способ запустить некоторый код с таймаутом в Python - PullRequest
28 голосов
/ 04 августа 2011

Я посмотрел онлайн и нашел несколько SO, обсуждающих и ActiveState рецепты для запуска некоторого кода с таймаутом. Похоже, есть несколько общих подходов:

  • Используйте поток, который запускает код, и join с таймаутом. Если истекло время ожидания - убить поток. Это напрямую не поддерживается в Python (используется закрытая _Thread__stop функция), поэтому плохая практика
  • Используйте signal.SIGALRM - но этот подход не работает в Windows !
  • Использовать подпроцесс с таймаутом - но это слишком тяжело - что если я хочу часто запускать прерываемое задание, я не хочу запускать процесс для каждого!

Итак, что такое правильный путь ? Я не спрашиваю об обходных путях (например, используйте Twisted и async IO), но о реальном способе решения актуальной проблемы - у меня есть какая-то функция, и я хочу запустить ее только с некоторым таймаутом. Если истекло время ожидания, я хочу вернуть контроль. И я хочу, чтобы он работал на Linux и Windows.

Ответы [ 9 ]

9 голосов
/ 11 августа 2011

Полностью общего решения этого действительно, честно говоря не существует.Вы должны использовать правильное решение для данного домена.

  • Если вы хотите, чтобы тайм-ауты для кода полностью контролировались, вы должны написать его для сотрудничества.Такой код должен каким-то образом разбиваться на маленькие кусочки, как в системе, управляемой событиями.Вы также можете сделать это с помощью многопоточности, если вы можете быть уверены, что ничто не будет удерживать блокировку слишком долго, но обработка блокировок на самом деле довольно сложна.вышла из-под контроля (например, если вы боитесь, что пользователь попросит ваш калькулятор вычислить 9**(9**9)), вам нужно запустить его в другом процессе.Это единственный простой способ достаточно изолировать его.Запустить его в вашей системе событий или даже в другом потоке будет недостаточно.Также возможно разбить вещи на маленькие кусочки, похожие на другое решение, но требует очень осторожного обращения и, как правило, того не стоит;в любом случае, это не позволяет вам сделать то же самое, что и просто запустить код Python.

9 голосов
/ 09 августа 2011

Возможно, вам понадобится модуль multiprocessing . Если subprocess слишком тяжелый, то это может не соответствовать вашим потребностям.

import time
import multiprocessing

def do_this_other_thing_that_may_take_too_long(duration):
    time.sleep(duration)
    return 'done after sleeping {0} seconds.'.format(duration)

pool = multiprocessing.Pool(1)
print 'starting....'
res = pool.apply_async(do_this_other_thing_that_may_take_too_long, [8])
for timeout in range(1, 10):
    try:
        print '{0}: {1}'.format(duration, res.get(timeout))
    except multiprocessing.TimeoutError:
        print '{0}: timed out'.format(duration) 

print 'end'
4 голосов
/ 04 марта 2014

Я нашел это с библиотекой событий:

http://eventlet.net/doc/modules/timeout.html

from eventlet.timeout import Timeout

timeout = Timeout(seconds, exception)
try:
    ... # execution here is limited by timeout
finally:
    timeout.cancel()
4 голосов
/ 11 августа 2011

Если это связано с сетью, вы можете попробовать:

import socket
socket.setdefaulttimeout(number)
2 голосов
/ 10 августа 2011

Другой способ - использовать faulthandler :

import time
import faulthandler


faulthandler.enable()


try:
    faulthandler.dump_tracebacks_later(3)
    time.sleep(10)
finally:
    faulthandler.cancel_dump_tracebacks_later()

Примечание: модуль faulthandler является частью stdlib в python3.3.

2 голосов
/ 05 августа 2011

Для «нормального» кода Python, который не затягивается в расширениях C или ожиданиях ввода-вывода, вы можете достичь своей цели, установив функцию трассировки с помощью sys.settrace(), которая прерывает работающий код по истечении времени ожидания .

Будет ли этого достаточно или нет, зависит от того, насколько вы работаете с вредоносным кодом. Если он хорошо себя ведет, функции трассировки достаточно.

0 голосов
/ 14 июня 2016

решение с помощью конструкции with и решение для слияния из -

  • Функция тайм-аута, если для завершения требуется слишком много времени
  • этой темыкоторые работают лучше.

    import threading, time
    
    class Exception_TIMEOUT(Exception):
        pass
    
    class linwintimeout:
    
        def __init__(self, f, seconds=1.0, error_message='Timeout'):
            self.seconds = seconds
            self.thread = threading.Thread(target=f)
            self.thread.daemon = True
            self.error_message = error_message
    
        def handle_timeout(self):
            raise Exception_TIMEOUT(self.error_message)
    
        def __enter__(self):
            try:
                self.thread.start()
                self.thread.join(self.seconds)
            except Exception, te:
                raise te
    
        def __exit__(self, type, value, traceback):
            if self.thread.is_alive():
                return self.handle_timeout()
    
    def function():
        while True:
            print "keep printing ...", time.sleep(1)
    
    try:
        with linwintimeout(function, seconds=5.0, error_message='exceeded timeout of %s seconds' % 5.0):
            pass
    except Exception_TIMEOUT, e:
        print "  attention !! execeeded timeout, giving up ... %s " % e
    
0 голосов
/ 16 июня 2015

Я решил это таким образом: для меня это прекрасно работает (в windows и совсем не тяжело), ​​я надеюсь, что это кому-нибудь пригодится)

import threading
import time

class LongFunctionInside(object):
    lock_state = threading.Lock()
    working = False

    def long_function(self, timeout):

        self.working = True

        timeout_work = threading.Thread(name="thread_name", target=self.work_time, args=(timeout,))
        timeout_work.setDaemon(True)
        timeout_work.start()

        while True:  # endless/long work
            time.sleep(0.1)  # in this rate the CPU is almost not used
            if not self.working:  # if state is working == true still working
                break
        self.set_state(True)

    def work_time(self, sleep_time):  # thread function that just sleeping specified time,
    # in wake up it asking if function still working if it does set the secured variable work to false
        time.sleep(sleep_time)
        if self.working:
            self.set_state(False)

    def set_state(self, state):  # secured state change
        while True:
            self.lock_state.acquire()
            try:
                self.working = state
                break
            finally:
                self.lock_state.release()

lw = LongFunctionInside()
lw.long_function(10)

Основная идея состоит в том, чтобысоздайте поток, который будет просто спать параллельно «долгой работе» и при пробуждении (после тайм-аута) изменить состояние защищенной переменной, при этом длинная функция проверяет защищенную переменную во время своей работы.Я довольно новичок в программировании на Python, поэтому, если в этом решении есть фундаментальные ошибки, такие как ресурсы, синхронизация, проблемы взаимоблокировок, пожалуйста, ответьте)).

0 голосов
/ 12 августа 2011

Если вы запускаете код, который, как вы ожидаете, умрет через определенное время, вы должны написать его правильно, чтобы не было никаких негативных последствий при завершении работы, независимо от того, является ли он потоком или подпроцессом. Шаблон команды с отменой был бы здесь полезен.

Итак, это действительно зависит от того, что делает поток, когда вы его убиваете. Если это просто хруст номера, кого это волнует, если вы его убьете. Если он взаимодействует с файловой системой, и вы убиваете его, то, возможно, вам следует действительно переосмыслить свою стратегию.

Что поддерживается в Python, когда дело доходит до потоков? Демон темы и соединения. Почему python позволяет основному потоку завершиться, если вы присоединились к демону, пока он еще активен? Потому что понятно, что кто-то, использующий потоки демона, (будем надеяться) напишет код так, чтобы это не имело значения, когда этот поток умирает. В этом контексте вполне допустимо дать тайм-аут для объединения, а затем дать основному объекту умереть и, таким образом, взять с собой все потоки демонов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...