Python: сделать временную задержку (спящий режим) для выполнения кода

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

Вступление

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

Другой пример - снижение нагрузки на сервер, с которым мы работаем. Например, при парсинге веб-страниц (этически) и соблюдении ToS рассматриваемого веб-сайта с соблюдением robots.txt - вы вполне можете отложить выполнение каждого запроса, чтобы не перегружать ресурсы сервера.

Многие запросы, отправляемые в быстрой последовательности, могут, в зависимости от рассматриваемого сервера, быстро задействовать все свободные соединения и фактически превратиться в DoS-атаку . Чтобы дать передышку, а также чтобы убедиться, что мы не оказываем негативного воздействия ни на пользователей веб-сайта, ни на сам веб-сайт - мы ограничили количество отправляемых запросов, задерживая каждый из них.

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

В этой статье мы рассмотрим, как отложить выполнение кода в Python - также известное как спящий режим . Это можно сделать несколькими способами:

Код задержки с time.sleep ()

Одним из наиболее распространенных решений проблемы является функция sleep() встроенного модуля time Он принимает количество секунд, в течение которых процесс должен засыпать - в отличие от многих других языков, которые основаны на миллисекундах :

 import datetime 
 import time 
 
 print(datetime.datetime.now().time()) 
 time.sleep(5) 
 print(datetime.datetime.now().time()) 

Это приводит к:

 14:33:55.282626 
 14:34:00.287661 

Совершенно ясно, что мы можем видеть задержку в 5 секунд между двумя print() с довольно высокой точностью - вплоть до второго десятичного знака. Если вы хотите спать менее 1 секунды, вы также можете легко передать нецелые числа:

 print(datetime.datetime.now().time()) 
 time.sleep(0.25) 
 print(datetime.datetime.now().time()) 

 14:46:16.198404 
 14:46:16.448840 

 print(datetime.datetime.now().time()) 
 time.sleep(1.28) 
 print(datetime.datetime.now().time()) 

 14:46:16.448911 
 14:46:17.730291 

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

time.sleep() есть один серьезный недостаток, очень заметный в многопоточных средах.

time.sleep () блокируется .

Он захватывает поток, в котором находится, и блокирует его на время сна . Это делает его непригодным для более длительного ожидания, так как в течение этого периода он забивает поток процессора. Кроме того, это делает его непригодным для асинхронных и реактивных приложений , которым часто требуются данные и обратная связь в реальном времени.

Еще одно замечание о time.sleep() - это то, что вы не можете его остановить. После запуска вы не можете отменить его извне, не завершив всю программу или sleep() генерировать исключение, которое остановило бы его.

Асинхронное и реактивное программирование

Асинхронное программирование вращается вокруг параллельного выполнения, когда задача может выполняться и завершаться независимо от основного потока.

В синхронном программировании - если функция A вызывает функцию B , она останавливает выполнение до тех пор, пока функция B не завершит выполнение, после чего функция A может возобновиться.

В асинхронном программировании - если функция A вызывает функцию B , независимо от ее зависимости результата от функции B , обе могут выполняться одновременно, и, если необходимо, дождаться завершения другой, чтобы использовать результаты друг друга.

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

И асинхронные, и реактивные приложения сильно страдают от блокировки кода, поэтому использование чего-то вроде time.sleep() для них не подходит. Давайте посмотрим на некоторые варианты задержки неблокирующего кода.

Код задержки с asyncio.sleep ()

Asyncio - это библиотека Python, предназначенная для написания параллельного кода и использующая async / await , который может быть знаком разработчикам, которые использовали его на других языках.

Установим модуль через pip :

 $ pip install asyncio 

После установки мы можем import его в наш скрипт и переписать нашу функцию:

 import asyncio 
 async def main(): 
 print(datetime.datetime.now().time()) 
 await asyncio.sleep(5) 
 print(datetime.datetime.now().time()) 
 
 asyncio.run(main()) 

При работе с asyncio мы помечаем функции, которые выполняются асинхронно, как async , и await результатов таких операций, как asyncio.sleep() которые будут завершены в какой-то момент в будущем.

Как и в предыдущем примере, это будет напечатано два раза с интервалом в 5 секунд:

 17:23:33.708372 
 17:23:38.716501 

Хотя на самом деле это не иллюстрирует преимущества использования asyncio.sleep() . Давайте перепишем пример для параллельного выполнения нескольких задач, где это различие гораздо более ясно:

 import asyncio 
 import datetime 
 
 async def intense_task(id): 
 await asyncio.sleep(5) 
 print(id, 'Running some labor-intensive task at ', datetime.datetime.now().time()) 
 
 async def main(): 
 await asyncio.gather( 
 asyncio.create_task(intense_task(1)), 
 asyncio.create_task(intense_task(2)), 
 asyncio.create_task(intense_task(3)) 
 ) 
 
 asyncio.run(main()) 

Здесь у нас есть async функция, которая имитирует трудоемкую задачу, выполнение которой занимает 5 секунд. Затем, используя asyncio , мы создаем несколько задач . Однако каждая задача может выполняться асинхронно, только если мы вызываем их асинхронно. Если бы мы запускали их последовательно, они также выполнялись бы последовательно.

Для их параллельного вызова мы используем gather() , которая собирает задачи и выполняет их:

 1 Running some labor-intensive task at 17:35:21.068469 
 2 Running some labor-intensive task at 17:35:21.068469 
 3 Running some labor-intensive task at 17:35:21.068469 

Все они выполняются одновременно, и время ожидания для трех из них не 15 секунд, а 5.

С другой стороны, если бы мы настроили этот код, чтобы вместо time.sleep()

 import asyncio 
 import datetime 
 import time 
 
 async def intense_task(id): 
 time.sleep(5) 
 print(id, 'Running some labor-intensive task at ', datetime.datetime.now().time()) 
 
 async def main(): 
 await asyncio.gather( 
 asyncio.create_task(intense_task(1)), 
 asyncio.create_task(intense_task(2)), 
 asyncio.create_task(intense_task(3)) 
 ) 
 
 asyncio.run(main()) 

Мы будем ждать 5 секунд между каждым оператором print()

 1 Running some labor-intensive task at 17:39:00.766275 
 2 Running some labor-intensive task at 17:39:05.773471 
 3 Running some labor-intensive task at 17:39:10.784743 

Код задержки с таймером

Класс Timer - это Thread , который может запускать и выполнять операции только по прошествии определенного периода времени. Это именно то, что мы ищем, хотя использование Thread s для задержки кода является излишним, если вы еще не работаете с многопоточной системой.

Класс Timer должен быть start() , и его можно остановить с помощью cancel() . Его конструктор принимает целое число, обозначающее количество секунд ожидания перед выполнением второго параметра - функции.

Создадим функцию и выполним ее через Timer :

 from threading import Timer 
 import datetime 
 
 def f(): 
 print("Code to be executed after a delay at:", datetime.datetime.now().time()) 
 
 print("Code to be executed immediately at:", datetime.datetime.now().time()) 
 timer = Timer(3, f) 
 timer.start() 

Это приводит к:

 Code to be executed immediately at: 19:47:20.032525 
 Code to be executed after a delay at: 19:47:23.036206 

Метод cancel() очень удобен, если у нас работает несколько функций, и мы хотели бы отменить выполнение функции на основе результатов другой или другого условия.

Напишем функцию f() , которая вызывает как f2() и f3() . f2() вызывается как есть - и возвращает случайное целое число от 1 до 10, имитируя время, необходимое для запуска этой функции.

f3() вызывается через Timer и если результат f2() больше 5 , f3() отменяется, тогда как если f2() выполняется в «ожидаемое» время меньше 5 - f3() запускается после таймер заканчивается:

 from threading import Timer 
 import datetime 
 import random 
 
 def f(): 
 print("Executing f1 at", datetime.datetime.now().time()) 
 result = f2() 
 timer = Timer(5, f3) 
 timer.start() 
 if(result > 5): 
 print("Cancelling f3 since f2 resulted in", result) 
 timer.cancel() 
 
 def f2(): 
 print("Executing f2 at", datetime.datetime.now().time()) 
 return random.randint(1, 10) 
 
 def f3(): 
 print("Executing f3 at", datetime.datetime.now().time()) 
 
 f() 

Выполнение этого кода несколько раз будет выглядеть примерно так:

 Executing f1 at 20:29:10.709578 
 Executing f2 at 20:29:10.709578 
 Cancelling f3 since f2 resulted in 9 
 
 Executing f1 at 20:29:14.178362 
 Executing f2 at 20:29:14.178362 
 Executing f3 at 20:29:19.182505 

Код задержки с событием

Класс Event можно использовать для генерации событий. Одно событие может быть "прослушано" несколькими потоками. Функция Event.wait() блокирует поток, в котором она находится, за исключением Event.isSet() . После того, как вы set() событие, все ожидающие потоки пробуждаются, и Event.wait() становится неблокирующим .

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

Давайте создадим waiter и запустим его несколько раз в разных потоках. Каждый официант начинает работать в определенное время и каждую секунду проверяет, работают ли они еще час, прямо перед тем, как принять заказ, на выполнение которого уходит секунда. Они будут работать, пока не будет назначено Событие, а точнее, их рабочее время не истечет.

У каждого официанта будет свой собственный поток, в то время как управление находится в основном потоке и звонит, когда каждый может позвонить домой. Поскольку сегодня они чувствуют себя очень щедрыми, они сократят рабочее время и отпустят официантов домой через 4 секунды работы:

 import threading 
 import time 
 import datetime 
 
 def waiter(event, id): 
 print(id, "Waiter started working at", datetime.datetime.now().time()) 
 event_flag = end_of_work.wait(1) 
 while not end_of_work.isSet(): 
 print(id, "Waiter is taking order at", datetime.datetime.now().time()) 
 event.wait(1) 
 if event_flag: 
 print(id, "Waiter is going home at", datetime.datetime.now().time()) 
 
 end_of_work = threading.Event() 
 
 for id in range(1, 3): 
 thread = threading.Thread(target=waiter, args=(end_of_work, id)) 
 thread.start() 
 
 end_of_work.wait(4) 
 end_of_work.set() 
 print("Some time passes, management was nice and cut the working hours short. It is now", datetime.datetime.now().time()) 

Выполнение этого кода приводит к:

 1 Waiter started working at 23:20:34.294844 
 2 Waiter started working at 23:20:34.295844 
 1 Waiter is taking order at 23:20:35.307072 
 2 Waiter is taking order at 23:20:35.307072 
 1 Waiter is taking order at 23:20:36.320314 
 2 Waiter is taking order at 23:20:36.320314 
 1 Waiter is taking order at 23:20:37.327528 
 2 Waiter is taking order at 23:20:37.327528 
 Some time passes, management was nice and cut the working hours short. It is now 23:20:38.310763 

end_of_work здесь использовалось для синхронизации двух потоков и управления, когда они работают, а когда нет, задерживая выполнение кода на установленное время между проверками.

Заключение

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

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

Используя asyncio , у нас есть асинхронная версия time.sleep() которую мы можем await .

Класс Timer задерживает выполнение кода и при необходимости может быть отменен.

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

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus