2014-11-17 2 views
0

Я хочу знать, имеет ли смысл использовать threading.Timer после получения запроса на выполнение задания через 30 минут. Посмотрим код:Использование threading.Timer после запроса на мою флэшку Приложение Python

def late_process(): 
    if not finish: 
     Timer(1800, late_process,()).start() 
     # Work to do ... 
     # Write logs, send emails... whatever 

@app.route('/timely-req') 
def timely(): 
    finish = False 
    Timer(1800, late_process,()).start() 
    return 'To be executed in 30 minutes' 

@app.route('/end-timely-req') 
def end(): 
    finish = True 
    return 'Process stopped' 

Итак, основная идея - инициировать выполнение (каждые 30 минут) этого процесса. Это работает, но я не знаю, является ли threading.Timer хорошей идеей, так как запрос вернется, но я покидаю сервер с постоянным потоком, просыпающимся каждые 30 минут. Это только или прототип, который будет использоваться и опробован, и это не - окончательное решение, которое когда-нибудь увидит производство.

Я использую Python 2.6, Flask 0.10.1 и Uwsgi.

ответ

6

threading.Timer имеет немного неуклюжий интерфейс (например, он не авто-повторяет каждые 30 минут, вам нужно снова его отпустить), и очевидный способ сделать это означает, что он постепенно затихает с течением времени) ,

Это также немного реалистично. если у вас много таймеров, у вас много потоков, что может быть проблемой для планировщика потоков вашей ОС.

Если у вас есть только один таймер, и вы работаете только каждые 30 минут, и все это для «фона», например, для опроса журнала и сводных писем, эти проблемы, вероятно, приемлемы - на самом деле, едва даже проблемы. Но есть проблемы, которые у вас есть.

Во-первых, выполнение функции finish = True внутри функции создает локальную переменную с именем finish, заслоняя любую глобальную переменную с тем же именем. Если вы хотите изменить глобальную переменную, необходимо global finish заявление в верхней части функции. *

Кроме того, каждый раз, когда кто-то бьет /timely-req, вы собираетесь выстрелить новый Timer. Это означает, что я мог бы легко выполнить DoS-сервер, возможно, даже случайно, просто запросив URL-адрес несколько сотен раз. Итак, если вы собираетесь это сделать, вы, вероятно, захотите сделать его одиночным объектом и создать таймер, если он еще не существует. Существуют также десятки реализаций однопоточных многопользовательских планировщиков по вкладам PyPI, ActiveState и Flask, которые автоматически позаботятся об этой проблеме (а также о двух проблемах выше, что вам, вероятно, и не нужно).

Кроме того, когда вы указываете, что флажок ушел, он прекращает принимать запросы, позволяет вашему коду завершить существующие запросы, позволяет любому потоку, который вы создали, завершить, а затем завершает работу. Если у вас есть нить, которая никогда не заканчивается, это не сработает, поэтому, если вы хотите изящную функцию выключения, вы должны добавить ее самостоятельно (установка finish = True).

И, наконец, не существует поточно-безопасной для обмена глобальной переменной между потоками без блокировки или другого устройства синхронизации потоков. В частности, теоретически возможно, что ваш поток сервера установит finish = True, и ваш поток таймера будет продолжать видеть старое значение в кэше навсегда. На практике, по крайней мере, с CPython, вам это не удастся. ** Но лучше писать код правильно.

* Кроме того, Flask имеет дополнительную функцию, которая подделывает глобальные переменные с помощью локальных объектов, то есть каждый поток получает свою собственную копию finish. И, поскольку каждый экземпляр Timer представляет собой новый поток, настройка finish = True не повлияет на него. Итак, если вы используете эту функцию, вы не можете использовать Timer таким образом. Но вы, вероятно, не используете его, если не знаете об этом.

** В CPython, подмена глобального переменный является атомарной операцией, но не блокирован, но из-за GIL, это будет на практике всегда доступно для каждого другого потока с помощью следующей GIL-кванта времени, который является доли секунды, поэтому ваша 30-минутная задержка даже не заметит. В худшем случае, он будет запускаться еще раз, чем вам хотелось бы - что уже могло произойти, если щелчок на веб-странице занял несколько миллисекунд дольше, чем вы ожидали.

+0

Я, наконец, установил 'apscheduler' для фоновых заданий, но не выполнил задание, которое я установил для его выполнения через 3 часа. Планировщик работает в том же процессе uwsgi, что и Flask, и я предполагаю, что uwsgi каким-то образом перезапускает его или что-то в этом роде. Я собираюсь запустить cron каждые 5 минут для выполнения необходимых своевременных задач. – licorna

+0

@licorna: Должно быть довольно легко определить, перезапускается ли процесс 'uwsgi' сервером/контейнером. Если у него другой pid, он перезапускается. (Вы можете просматривать это извне с помощью 'ps' или просто записывать' os.getpid() 'так часто. Вы даже можете зарегистрировать что-то при запуске и использовать' atexit' для входа в систему при завершении работы. – abarnert

Смежные вопросы