timerfd.md
1> **Источник:** https://python-all.ru/3.14/howto/timerfd.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# HOWTO по файловому дескриптору таймера89**Версия:**10111.131213В этом HOWTO рассматривается поддержка в Python файлового дескриптора таймера Linux.1415## Примеры1617В следующем примере показано, как использовать файловый дескриптор таймера для выполнения функции два раза в секунду:1819```python20# Практические скрипты должны использовать неблокирующий таймер,21# здесь для простоты мы используем блокирующий таймер.22import os, time2324# Создаём файловый дескриптор таймера25fd = os.timerfd_create(time.CLOCK_REALTIME)2627# Запускаем таймер через 1 секунду с интервалом в полсекунды.28os.timerfd_settime(fd, initial=1, interval=0.5)2930try:31 # Обрабатываем события таймера четыре раза.32 for _ in range(4):33 # read() будет блокироваться, пока таймер не сработает.34 _ = os.read(fd, 8)35 print("Timer expired")36finally:37 # Не забудьте закрыть файловый дескриптор таймера!38 os.close(fd)39```4041Чтобы избежать потери точности, вызванной типом [`float`](https://python-all.ru/3.14/library/functions.html#float), файловые дескрипторы таймера позволяют задавать начальное время срабатывания и интервал в целых наносекундах с помощью вариантов функций `_ns`.4243Этот пример показывает, как [`epoll()`](https://python-all.ru/3.14/library/select.html#select.epoll) можно использовать с файловыми дескрипторами таймера для ожидания, пока файловый дескриптор не будет готов к чтению:4445```python46import os, time, select, socket, sys4748# Создаём объект epoll.49ep = select.epoll()5051# В этом примере используется адрес обратной петли для отправки команды "stop" на сервер.52#53# $ telnet 127.0.0.1 123454# Пытаемся подключиться к 127.0.0.1...55# Подключено к 127.0.0.1.56# Экранирующий символ – '^]'.57# стоп58# Соединение закрыто внешним хостом.59#60sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)61sock.bind(("127.0.0.1", 1234))62sock.setblocking(False)63sock.listen(1)64ep.register(sock, select.EPOLLIN)6566# Создавать файловые дескрипторы таймера в неблокирующем режиме.67num = 368fds = []69for _ in range(num):70 fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)71 fds.append(fd)72 # Зарегистрировать файловый дескриптор таймера для событий чтения73 ep.register(fd, select.EPOLLIN)7475# Запустить таймер с помощью os.timerfd_settime_ns() в наносекундах.76# Таймер 1 срабатывает каждые 0.25 секунд, таймер 2 каждые 0.5 секунд и т.д.77for i, fd in enumerate(fds, start=1):78 one_sec_in_nsec = 10**979 i = i * one_sec_in_nsec80 os.timerfd_settime_ns(fd, initial=i//4, interval=i//4)8182timeout = 383try:84 conn = None85 is_active = True86 while is_active:87 # Ожидать истечения таймера в течение 3 секунд.88 # epoll.poll() возвращает список пар (fd, событие).89 # fd – это файловый дескриптор.90 # sock и conn[=возвращаемое значение socket.accept()] – это объекты сокетов, а не файловые дескрипторы.91 # Поэтому используйте sock.fileno() и conn.fileno() для получения файловых дескрипторов.92 events = ep.poll(timeout)9394 # Если несколько файловых дескрипторов таймера готовы к чтению одновременно,95 # epoll.poll() возвращает список пар (fd, событие).96 #97 # В данном примере настроек:98 # 1-й таймер срабатывает каждые 0,25 секунды через 0,25 секунды. (0,25; 0,5; 0,75; 1,0; ...)99 # 2-й таймер – каждые 0,5 секунды через 0,5 секунды. (0,5; 1,0; 1,5; 2,0; ...)100 # 3-й таймер – каждые 0,75 секунды через 0,75 секунды. (0,75; 1,5; 2,25; 3,0; ...)101 #102 # Через 0,25 секунды срабатывает только 1-й таймер.103 # Через 0,5 секунды 1-й и 2-й таймеры срабатывают одновременно.104 # Через 0,75 секунды 1-й и 3-й таймеры срабатывают одновременно.105 # Через 1,5 секунды 1-й, 2-й и 3-й таймеры срабатывают одновременно.106 #107 # Если файловый дескриптор таймера был сигнализирован более одного раза с момента108 # последнего вызова os.read(), os.read() возвращает количество сигналов109 # в виде целого числа в порядке хоста (байты).110 print(f"Signaled events={events}")111 for fd, event in events:112 if event & select.EPOLLIN:113 if fd == sock.fileno():114 # Проверить, есть ли запрос на подключение.115 print(f"Accepting connection {fd}")116 conn, addr = sock.accept()117 conn.setblocking(False)118 print(f"Accepted connection {conn} from {addr}")119 ep.register(conn, select.EPOLLIN)120 elif conn and fd == conn.fileno():121 # Проверить, есть ли данные для чтения.122 print(f"Reading data {fd}")123 data = conn.recv(1024)124 if data:125 # Для безопасности следует перехватывать исключение UnicodeDecodeError.126 cmd = data.decode()127 if cmd.startswith("stop"):128 print(f"Stopping server")129 is_active = False130 else:131 print(f"Unknown command: {cmd}")132 else:133 # Данных больше нет, закрыть соединение.134 print(f"Closing connection {fd}")135 ep.unregister(conn)136 conn.close()137 conn = None138 elif fd in fds:139 print(f"Reading timer {fd}")140 count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)141 print(f"Timer {fds.index(fd) + 1} expired {count} times")142 else:143 print(f"Unknown file descriptor {fd}")144finally:145 for fd in fds:146 ep.unregister(fd)147 os.close(fd)148 ep.close()149```150151Этот пример показывает, как [`select()`](https://python-all.ru/3.14/library/select.html#select.select) можно использовать с файловыми дескрипторами таймера для ожидания, пока файловый дескриптор не будет готов к чтению:152153```python154import os, time, select, socket, sys155156# В этом примере используется адрес обратной петли для отправки команды "stop" на сервер.157#158# $ telnet 127.0.0.1 1234159# Пытаемся подключиться к 127.0.0.1...160# Подключено к 127.0.0.1.161# Экранирующий символ – '^]'.162# стоп163# Соединение закрыто внешним хостом.164#165sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)166sock.bind(("127.0.0.1", 1234))167sock.setblocking(False)168sock.listen(1)169170# Создавать файловые дескрипторы таймера в неблокирующем режиме.171num = 3172fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)173 for _ in range(num)]174select_fds = fds + [sock]175176# Запускать таймеры с помощью os.timerfd_settime() в секундах.177# Таймер 1 срабатывает каждые 0.25 секунд, таймер 2 каждые 0.5 секунд и т.д.178for i, fd in enumerate(fds, start=1):179 os.timerfd_settime(fd, initial=i/4, interval=i/4)180181timeout = 3182try:183 conn = None184 is_active = True185 while is_active:186 # Ожидать истечения таймера в течение 3 секунд.187 # select.select() возвращает список файловых дескрипторов или объектов.188 rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)189 for fd in rfd:190 if fd == sock:191 # Проверить, есть ли запрос на подключение.192 print(f"Accepting connection {fd}")193 conn, addr = sock.accept()194 conn.setblocking(False)195 print(f"Accepted connection {conn} from {addr}")196 select_fds.append(conn)197 elif conn and fd == conn:198 # Проверить, есть ли данные для чтения.199 print(f"Reading data {fd}")200 data = conn.recv(1024)201 if data:202 # Для безопасности следует перехватывать исключение UnicodeDecodeError.203 cmd = data.decode()204 if cmd.startswith("stop"):205 print(f"Stopping server")206 is_active = False207 else:208 print(f"Unknown command: {cmd}")209 else:210 # Данных больше нет, закрыть соединение.211 print(f"Closing connection {fd}")212 select_fds.remove(conn)213 conn.close()214 conn = None215 elif fd in fds:216 print(f"Reading timer {fd}")217 count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)218 print(f"Timer {fds.index(fd) + 1} expired {count} times")219 else:220 print(f"Unknown file descriptor {fd}")221finally:222 for fd in fds:223 os.close(fd)224 sock.close()225 sock = None226```227