タイマーファイル記述子 HOWTO¶
- リリース:
1.13
この HOWTO では、Linux のタイマーファイル記述子を Python で使う方法を説明します。
使用例¶
次の例は、タイマーファイル記述子を使って 1 秒に 2 回関数を実行する方法を示します。
# 実用的なスクリプトではノンブロッキングのタイマーを使うべきだが、
# ここでは簡単にするためブロッキングのタイマーを使う。
import os, time
# タイマーファイル記述子を作成する
fd = os.timerfd_create(time.CLOCK_REALTIME)
# 1 秒後に初回の満了を迎え、その後は 0.5 秒間隔とする
os.timerfd_settime(fd, initial=1, interval=0.5)
try:
# タイマーのイベントを 4 回処理する。
for _ in range(4):
# タイマーが満了するまで read() はブロックする
_ = os.read(fd, 8)
print("Timer expired")
finally:
# 忘れずにタイマーファイル記述子を閉じる。
os.close(fd)
float 型による精度の低下を避けるため、タイマーファイル記述子の関数には _ns 版が用意されています。
これらを使うと、最初にタイマーが満了するまでの時間と間隔を、整数のナノ秒単位で指定できます。
この例は、タイマーファイル記述子と epoll() を組み合わせ、ファイル記述子が読み取り可能になるまで待つ方法を示します。
import os, time, select, socket, sys
# epoll オブジェクトを作成する
ep = select.epoll()
# この例ではループバックアドレスでサーバーに "stop" コマンドを送る。
#
# $ telnet 127.0.0.1 1234
# Trying 127.0.0.1...
# Connected to 127.0.0.1.
# Escape character is '^]'.
# stop
# Connection closed by foreign host.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 1234))
sock.setblocking(False)
sock.listen(1)
ep.register(sock, select.EPOLLIN)
# ノンブロッキングモードでタイマーファイル記述子を作成する。
num = 3
fds = []
for _ in range(num):
fd = os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
fds.append(fd)
# タイマーファイル記述子を読み取りイベント用に登録する
ep.register(fd, select.EPOLLIN)
# os.timerfd_settime_ns() でナノ秒単位の時間を指定してタイマーを開始する。
# タイマー 1 は 0.25 秒間隔、タイマー 2 は 0.5 秒間隔、以下も同様。
for i, fd in enumerate(fds, start=1):
one_sec_in_nsec = 10**9
i = i * one_sec_in_nsec
os.timerfd_settime_ns(fd, initial=i//4, interval=i//4)
timeout = 3
try:
conn = None
is_active = True
while is_active:
# タイマーの満了を最大 3 秒待つ。
# epoll.poll() は (fd, event) のペアのリストを返す。
# fd はファイル記述子。
# sock と conn [socket.accept() の戻り値] はファイル記述子ではなくソケットオブジェクト。
# そのため、ファイル記述子は sock.fileno() と conn.fileno() で取得する。
events = ep.poll(timeout)
# 複数のタイマーファイル記述子が同時に読み取り可能になった場合、
# epoll.poll() は (fd, event) のペアのリストを返す。
#
# この例の設定では、
# 第 1 タイマーは 0.25 秒後から 0.25 秒間隔で満了する。(0.25, 0.5, 0.75, 1.0, ...)
# 第 2 タイマーは 0.5 秒後から 0.5 秒間隔。(0.5, 1.0, 1.5, 2.0, ...)
# 第 3 タイマーは 0.75 秒後から 0.75 秒間隔。(0.75, 1.5, 2.25, 3.0, ...)
#
# 0.25 秒後には第 1 タイマーだけが満了する。
# 0.5 秒後には第 1 タイマーと第 2 タイマーが同時に満了する。
# 0.75 秒後には第 1 タイマーと第 3 タイマーが同時に満了する。
# 1.5 秒後には第 1、第 2、第 3 タイマーが同時に満了する。
#
# タイマーファイル記述子が前回の os.read() の呼び出し以降に
# 複数回通知された場合、os.read() は通知回数を
# ホストのバイト順の bytes オブジェクトとして返す。
print(f"Signaled events={events}")
for fd, event in events:
if event & select.EPOLLIN:
if fd == sock.fileno():
# 接続要求があるか確認する。
print(f"Accepting connection {fd}")
conn, addr = sock.accept()
conn.setblocking(False)
print(f"Accepted connection {conn} from {addr}")
ep.register(conn, select.EPOLLIN)
elif conn and fd == conn.fileno():
# 読み取るデータがあるか確認する。
print(f"Reading data {fd}")
data = conn.recv(1024)
if data:
# 安全のため、UnicodeDecodeError 例外を捕捉するべき。
cmd = data.decode()
if cmd.startswith("stop"):
print(f"Stopping server")
is_active = False
else:
print(f"Unknown command: {cmd}")
else:
# これ以上データがないので接続を閉じる
print(f"Closing connection {fd}")
ep.unregister(conn)
conn.close()
conn = None
elif fd in fds:
print(f"Reading timer {fd}")
count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
print(f"Timer {fds.index(fd) + 1} expired {count} times")
else:
print(f"Unknown file descriptor {fd}")
finally:
for fd in fds:
ep.unregister(fd)
os.close(fd)
ep.close()
この例は、タイマーファイル記述子と select() を組み合わせ、ファイル記述子が読み取り可能になるまで待つ方法を示します。
import os, time, select, socket, sys
# In this example, use loopback address to send "stop" command to the server.
#
# $ telnet 127.0.0.1 1234
# Trying 127.0.0.1...
# Connected to 127.0.0.1.
# Escape character is '^]'.
# stop
# Connection closed by foreign host.
#
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 1234))
sock.setblocking(False)
sock.listen(1)
# Create timer file descriptors in non-blocking mode.
num = 3
fds = [os.timerfd_create(time.CLOCK_REALTIME, flags=os.TFD_NONBLOCK)
for _ in range(num)]
select_fds = fds + [sock]
# Start the timers with os.timerfd_settime() in seconds.
# Timer 1 fires every 0.25 seconds; timer 2 every 0.5 seconds; etc
for i, fd in enumerate(fds, start=1):
os.timerfd_settime(fd, initial=i/4, interval=i/4)
timeout = 3
try:
conn = None
is_active = True
while is_active:
# Wait for the timer to expire for 3 seconds.
# select.select() returns a list of file descriptors or objects.
rfd, wfd, xfd = select.select(select_fds, select_fds, select_fds, timeout)
for fd in rfd:
if fd == sock:
# Check if there is a connection request.
print(f"Accepting connection {fd}")
conn, addr = sock.accept()
conn.setblocking(False)
print(f"Accepted connection {conn} from {addr}")
select_fds.append(conn)
elif conn and fd == conn:
# Check if there is data to read.
print(f"Reading data {fd}")
data = conn.recv(1024)
if data:
# You should catch UnicodeDecodeError exception for safety.
cmd = data.decode()
if cmd.startswith("stop"):
print(f"Stopping server")
is_active = False
else:
print(f"Unknown command: {cmd}")
else:
# No more data, close connection
print(f"Closing connection {fd}")
select_fds.remove(conn)
conn.close()
conn = None
elif fd in fds:
print(f"Reading timer {fd}")
count = int.from_bytes(os.read(fd, 8), byteorder=sys.byteorder)
print(f"Timer {fds.index(fd) + 1} expired {count} times")
else:
print(f"Unknown file descriptor {fd}")
finally:
for fd in fds:
os.close(fd)
sock.close()
sock = None