Доброго времени суток. Пару дней назад начал изучать Python и в качестве первой программы решил реализовать «метроном», который печатает что-нибудь в стандартный вывод раз в какое-то время. Вот скелет моей программы:
#!/usr/bin/python3
import time
while True:
print('Tick')
time.sleep(1)
Но даже такая программа ведет себя очень странно: сообщение может задержаться на пару секунд, а потом резко появиться дважды или в течении нескольких тактов ощутимо подтормаживать, а затем внезапно ускориться. Вообщем, работает не стабильно и криво.
Может, есть другой, более точный способ реализации цикла с задержкой? Пол дня вчера гуглил на тему «python metronome», везде используют именно time.sleep. Я в растерянности.
Как тут сказали два анонимуса, могут влиять 2 (или 3, кому как больше нравится) фактора. 1) Буферизация вывода, 2) шедулеры ОС с питоновским рантаймом и 3) суть вызывания слипа последовательно с кодом.
Давай выясним, в чем дело. Чуть усовершенствую твой скрипт.
#!/usr/bin/python3
import time
cur_time = lambda: int(round(time.time() * 1000))
while True:
print('{} tick'.format(cur_time()))
time.sleep(1)
Ты увидешь что-то вроде. Ты тоже покажи свой вывод
Числа - время в миллисекундах. Во-первых, обрати внимание на равномерность появления сообщений, как раз то, о чем ты говоришь в своем посте. Они появляются неравномерно, как я понимаю? При этом, числа нам говорят о том, что секунда отсчитывается с точностью до 1-2 миллисекунд.
Запускай. Сообщения должны теперь появляться более равномерно. Я сейчас не знаю как это автоматически протестировать, поэтому остается полагаться на субьективное восприятие информации с экрана. Полагаю, потребуется специальный стенд для снимков с экрана и анализа равномерности вывода сообщений.
Итак, при flush=True мы уменьшили влияние буфера вывода на равномерность появления сообщений.
Далее, можно заметить, что выводимые значения времени «убегают». Т.е. вывод сообщений получается не через 1.0 секунд, а через 1.001 (или другое значение). Это влияние кумулятивного эффекта от шедулера и того, что слип за слипом вызывается последовательно, перемежаясь с выполенением кода питоновского кода, на которое тоже надо потратить время. На самом деле, влияние шедулера намного меньше, чем может показаться, и дело только в последовательности «слип-код-слип-код».
В итоге, картина мне видится такой: максимальное влияние имеет буферизация вывода (нужно делать Flush=True для избежания). Затем, последовательный вызов кода со слипом; это на гране заметного. А шедулер и рантайм питона имеет минимальное влияние, которое уже не важно для данной задачи.
Анонимус, упоминавший «реалтайм» и сишечку, занимается преждевременной оптимизацией и забегает не в ту степь, но дает толковый совет про select, конечно. Нужно понимать, как работают библиотеки питона «под капотом» и как работают ОС. А отсутствие flush и последовательный sleep c кодом можно и в Сишечке наблюдать.
А ты даже не потрудился понять, в чем дело, что говорит о том, что твой код на расте будет не эффективней кода ТС на питоне, или вооон того парня на PHP
Вывод на экран по-прежнему тормозит но я понял, что это неочевидные тормоза эмулятора терминала. В текстовой консоли без иксов печатает без рывков. Это не критично, т.к. задача метронома издавать звуки, просто неравномерность вывода смутила.
Второй пример я вообще не понял (не дошел еще до потоков), так что пока обойдусь слипом. В любом случае, спасибо тебе и анонам.
Тут нужна сишка/C++/rust, при том вся работа должна быть в разных потоках, иначе будет накапливаться отставание.
Мусье не порите чушь. Потоки также будут дрифтить. Единственное решение, учитывать отклонение.
import time
def sleep(until):
duration = until - time.time()
if duration > 0:
time.sleep(duration)
def intervals(period):
now = time.time()
while True:
now += period
yield now
for until in intervals(1):
sleep(until)
now = time.time()
print('now: {}, diff: {}'.format(now, now - until, flush=True))
Там инвариант явный. Поэтому формально доказывается что убегания не будет. Более того, этот инвариант позволяет выполнять произвольную работу между sleep, и все равно убегания не будет, если среднее время работы меньше периода.
Но это явно выше твоего уровня. Учитесь, мусье, вам еще много грызть.
Раз идет речь про обучение, то советую абстрагироваться от задачи метронома и пилить идеально работающий таймер (без привязки к потокам ввода-вывода), который будет выполнять всего 1 задачу - дергать коллбек в нужное время (асинхрнонно, конечно же). Буферизация-шмуферизация - это проблема уже из другой плоскости. Пусть её решает тот, кто отвечает за функцию-коллбек. Не твоя проблема (по крайней мере, пока ты работаешь над таймером). Учись.