無名パイプと名前付きパイプ
パイプとは?
コンピュータ上で動作している異なるプロセス(プログラム)間でデータを受け渡すための仕組み。これを水道管のイメージで考えると、一方のプロセスがデータを「水」として流し込み、もう一方のプロセスがそれを受け取って利用する、というイメージ。
例えば、あるプログラムAの出力を別のプログラムBの入力として使いたい場合、パイプを使うと便利。
パイプには主に「無名パイプ」と「名前付きパイプ」の2種類がある。
無名パイプと名前付きパイプ
無名パイプ
- 親子関係にあるプロセス間でのみ使用できる。
- 一時的な通信チャネルで、プロセスが終了すると消える。
- 単方向の通信のみ可能。
- ファイルシステム上に実体を持たない。
名前付きパイプ(FIFO)
- 普通のファイルのように名前を持ち、ファイルシステム上に実体(特殊なファイル)を持つが、実際にはメモリ上でデータをやり取りする。
- ファイルシステム上に名前を持つため、関係のない複数のプロセス間で使用できる。
- 永続的に存在し、複数のプロセスで再利用可能。
- 双方向の通信が可能。
両者の違い
無名パイプは、例えると家族間での会話のようなもので、同じ家に住む家族(親子関係にあるプロセス)同士なら、特別な準備なしですぐに会話(通信)ができる、というイメージ。
名前付きパイプは、電話のようなイメージ。知らない人(関係のないプロセス)とも、電話番号(パイプの名前)さえ分かれば通信できる。また、電話機(ファイルシステム上の実体)が必要。
両者の違いを表でまとめると、以下のようになる。
特徴 | 無名パイプ | 名前付きパイプ |
---|---|---|
名前 | なし | あり(ファイルシステム上) |
使用可能なプロセス | 親子関係のみ | 任意のプロセス |
存続期間 | プロセス終了まで | 永続的 |
通信方向 | 単方向 | 双方向 |
再利用性 | 不可 | 可能 |
無名パイプを使った簡易な親子プロセス間通信
以下に、pythonでのコード例を示す。
import os
# パイプを作成
read_fd, write_fd = os.pipe()
# 子プロセスを作成
pid = os.fork()
if pid > 0: # 親プロセス
os.close(read_fd) # 読み取り側を閉じる
print("親プロセス: メッセージを送信します")
message = "こんにちは、子プロセス!"
os.write(write_fd, message.encode()) # メッセージを送信
os.close(write_fd) # 書き込み側を閉じる
else: # 子プロセス
os.close(write_fd) # 書き込み側を閉じる
print("子プロセス: メッセージを待っています")
message = os.read(read_fd, 1024).decode() # メッセージを受信
print(f"子プロセス: 受け取ったメッセージ: {message}")
os.close(read_fd) # 読み取り側を閉じる
上記のプログラムについての説明
-
read_fd, write_fd = os.pipe()
でパイプを作成し、読み取り用と書き込み用の2つのファイルディスクリプタを得る。 -
pid = os.fork()
で子プロセスを作成する。この時点で、親プロセスと子プロセスの両方がパイプにアクセスできる。 - 親プロセスは書き込み用、子プロセスは読み取り用のパイプを使う。
- 以下のように、使わない側は閉じる。
# 親プロセス
os.close(read_fd) # 読み取り側を閉じる
# 子プロセス
os.close(write_fd) # 書き込み側を閉じる
- データの送受信には os.write() と os.read() を使う。Python の文字列をバイト列に変換(encode)したり、その逆(decode)をする必要がある(コードの以下の部分参照)。
#親プロセス
os.write(write_fd, message.encode()) # メッセージを送信
#子プロセス
message = os.read(read_fd, 1024).decode() # メッセージを受信
上記のプログラムを実行すると、以下のような出力が得られる。
親プロセス: メッセージを送信します
子プロセス: メッセージを待っています
子プロセス: 受け取ったメッセージ: こんにちは、子プロセス!
名前付きパイプを使った簡易なクライアント-サーバー間通信
以下に、名前付きパイプを使用する、2つの別Pythonスクリプト(server.pyとserver.py)の例を示す。
スクリプトの使用方法
まず、server.pyを実行する。
次に、別のターミナルウィンドウでclient.pyを実行する。
クライアント側でメッセージを入力すると、サーバー側でそのメッセージが表示される。
これらserver.pyとclient.pyのプログラムを使用することで、異なるプロセス(サーバーとクライアント)間でメッセージをやり取りできる。
{
"filepath": "data/temp/pipe",
"max_connections": 5,
"timeout": 30
}
import os
import json
import time
# 設定ファイルを読み込む
with open('config.json', 'r') as config_file:
config = json.load(config_file)
# 名前付きパイプを作成
if not os.path.exists(config['filepath']):
os.mkfifo(config['filepath'])
print(f"サーバーが起動しました。パイプの場所: {config['filepath']}")
try:
while True:
# パイプを開いてメッセージを待つ
with open(config['filepath'], 'r') as pipe:
message = pipe.read()
if message:
print(f"受信したメッセージ: {message}")
# ここで受信したメッセージを処理する
# 例: データベースに保存したり、他のクライアントに転送したりする
time.sleep(1) # CPUの負荷を減らすために少し待つ
except KeyboardInterrupt:
print("サーバーを終了します。")
finally:
# プログラム終了時にパイプを削除
os.remove(config['filepath'])
サーバープログラムについての説明
- 設定ファイル(config.json)を読み込み、
os.mkfifo(config['filepath'])
で名前付きパイプを作成する[1]。これはファイルシステム上に特殊なファイルとして現れる。
-
while Trueの無限ループでパイプからのメッセージを待ち受ける。
ここで無限ループにしているのは、- サーバーを常時稼働状態にする
- 複数のクライアントからの接続を継続的に受け付ける
ためである。
-
メッセージを受信したら、
print(f"受信したメッセージ: {message}")
で、メッセージを表示する(実際のアプリケーションではここでメッセージを処理する)。 -
Ctrl+Cを押すと、whileループを抜けて
except KeyboardInterrupt
[2]ブロックに入る。
- そのあと、finallyブロックでパイプを削除し、プログラムを終了する。サーバーは通常、最後に終了するプロセスなので、他のプロセスがまだパイプを使用している可能性を考慮するため、パイプの削除もサーバー側で行うのが一般的。
import os
import json
# 設定ファイルを読み込む
with open('config.json', 'r') as config_file:
config = json.load(config_file)
print(f"クライアントが起動しました。パイプの場所: {config['filepath']}")
try:
while True:
# ユーザーからの入力を受け取る
message = input("送信するメッセージを入力してください(終了するには'exit'と入力): ")
if message.lower() == 'exit':
break
# パイプにメッセージを書き込む
with open(config['filepath'], 'w') as pipe:
pipe.write(message)
print("メッセージを送信しました。")
except KeyboardInterrupt:
print("クライアントを終了します。")
サーバープログラムについての説明
- 設定ファイル(config.json)を読み込む。
- whileループに入る。
-
message = input("送信するメッセージを入力してください(終了するには'exit'と入力): ")
の部分で、ユーザーからの入力を受け付ける。 - 入力されたメッセージをパイプに書き込む。
- ユーザーが'exit'と入力するまで、この処理を繰り返す。
- Ctrl+Cを押すと、whileループを抜けてexcept KeyboardInterruptブロックに入り、プログラムは終了する。
プログラムで作成した名前付きパイプについて補足
- os.mkfifo() で名前付きパイプを作成する。これはファイルシステム上に特殊なファイルとして現れる。
- 通常のファイル操作(open, read, write)でパイプを扱うことができる。
- 送信側と受信側は独立したプロセスで、同じパイプ名を使って通信する。
- 使用後はos.remove()でパイプを削除する。
まとめ
パイプは、異なるプロセス間でデータを効率的に受け渡すための重要な仕組み。無名パイプと名前付きパイプ(FIFO)の2種類があり、それぞれ特徴が異なる。無名パイプは親子プロセス間の一時的な通信に適しており、名前付きパイプはファイルシステム上に実体を持ち、関連のないプロセス間でも通信が可能。両者の違いは、名前の有無、使用可能なプロセス、存続期間、通信方向、再利用性にある。
パイプの特性を理解し、適切に使用することで、効率的なプロセス間通信が実現でき、より柔軟で強力なシステム設計が可能となる。
Discussion