Zenn
📂

【Python】プロセス間通信の基本(パイプと名前付きパイプ)

に公開
1

概要

以前、プロセスの基礎の基礎の記事を書きました。

https://zenn.dev/shimiyu/articles/4f432e6ea1be06

プロセスに続き、パイプについて書きました。

パイプ(pipe)とは

プロセス間でデータをやり取りする場面があります。例えば、親プロセスが生成したメッセージを子プロセスに渡して処理させる、といったケースです。

この時に使うのが パイプ(pipe) です。文字通り、プロセス間を繋ぐ管(くだ)の役割を果たします。

Pythonではos.pipe()を使うことで、プロセス間でデータをやり取りするための通路(パイプ)を作ることができます。

以下は親プロセスで生成したメッセージを子プロセスで受信し表示する簡単なサンプルコードです。

pipe.py
import os

r, w = os.pipe()
pid = os.fork()

if pid > 0:
    # 親プロセスは書き込みしかしないため、読み取り側は閉じる
    os.close(r)

    message = f'親プロセス{os.getpid()}からのメッセージ'
    print(f'[親]: メッセージを送信しました。 : {message}')
    # 書き込み専用のパイプにメッセージを書き込む
    os.write(w, message.encode('utf_8'))

else:
    # 子プロセスは読み取りしかしないため、書き込み側は閉じる
    os.close(w)

    print(f'[子]: pid: {os.getpid()}')
    # 読み取り専用のパイプからメッセージを読み取ります
    with os.fdopen(r) as f:
        messageFromParent = f.read()
        print(f'[子]: 親から受け取ったメッセージ: {messageFromParent}')

出力結果は以下のとおりです。

python3 pipe.py
[]: メッセージを送信しました。 : 親プロセス39219からのメッセージ
[]: pid: 39220
[]: 親から受け取ったメッセージ: 親プロセス39219からのメッセージ

パイプは一方向

サンプルでは、1組の 読み取り口(r)書き込み口(w) を作っています。

r, w = os.pipe()

パイプは、一方向の通信しかできないという特徴を持ちます。つまり、親が書き込んだら、そのパイプには子は書き込めません。逆も然りです。

もし、双方向の通信(親→子、子→親)をしたい場合、パイプを2本用意する必要があります。

# パイプ1:親 → 子
r1, w1 = os.pipe()

# パイプ2:子 → 親
r2, w2 = os.pipe()

rとw: ファイルディスクリプタ

os.pipe()が返すrwは、それぞれ「ファイルディスクリプタ(file descriptor)」と呼ばれるものです。

r, w = os.pipe()

ファイルディスクリプタとは、OSがファイルやパイプなど、読み書き可能なリソースを識別するために使う「番号」のようなものです。Pythonでは整数として扱います。

r, w = os.pipe()  

print("r", r)
print("w", w)

# 出力結果
r 3
w 4

実際に出力してみると、rwの正体が整数であることが分かります。

OSには、あらかじめ以下のような標準的なディスクリプタが設定されています。

番号 名前 説明
0 標準入力 (stdin) キーボードやファイルなどからの入力を受け取る
1 標準出力 (stdout) プログラムの通常の出力先(通常は画面)
2 標準エラー出力 (stderr) エラーメッセージなどを出力する先(通常は画面)

そのため、os.pipe()で生成されるパイプ用のディスクリプタには、通常3以上の整数が割り当てられます。

名前付きパイプ

名前付きパイプとは

名前付きパイプ(Named Pipe または FIFO)は、プロセス間通信に使われる仕組みの1つです。通常のパイプ(os.pipe())とは異なり、親子関係を持たない独立したプロセス同士でも通信できる特徴があります。

名前付きパイプの実態は、ファイルシステム上に存在する特殊なファイルです。見た目はただのファイルですが、このファイルにデータを書き込んだり読み取ったりすることで、プロセス間でデータをやり取りするための通路としての役割を果たします。

実際に、パイプを作成後にls -lコマンドで確認すると、該当ファイルが表示されます。通常のファイルとは異なり、先頭にpが付いた状態で表示されることが分かります。

Pipe % ls -l /tmp/notify_pipe
prw-r--r--  1 user  wheel  0  3 29 21:09 /tmp/notify_pipe

このように、名前付きパイプは 「ファイルパス」を通じて通信を行う仕組みであり、完全に独立したプロセス同士でもやり取りが可能になります。

後ほどサンプルで見ますが、Pythonではos.mkfifo()を使って、このような名前付きパイプを作成できます。

通常のパイプとの違い

種類 対象プロセス 識別方法 用途
通常のパイプ 主に親子プロセス間 ファイルディスクリプタ 一時的な通信、簡易な処理
名前付きパイプ 独立したプロセス同士も可 ファイルパス 長寿命な通信、プロセス分離型設計

名前付きパイプのサンプル

以下は、名前付きパイプを使ったシンプルな通知システム風のサンプルです。
一方のプロセス(notifier.py)が定期的にメッセージを送信し、もう一方のプロセス(listener.py)がそのメッセージをリアルタイムで受信して表示します。

それぞれのプロセスは別々に起動され、完全に独立して動作していますが、共通の名前付きパイプを通じて通信できている点がポイントです。

notifier.py
import os
import time
from datetime import datetime

fifo_path = '/tmp/notify_pipe'

# すでに存在するなら削除して作り直す
if os.path.exists(fifo_path):
    os.remove(fifo_path)

# 指定したパスに「名前付きパイプ(FIFO)」を作成する
os.mkfifo(fifo_path)

notifications = [
    "サンプル通知スタート",
    "通知1",
    "通知2",
    "通知3",
    "通知を終了します。"
]

with open(fifo_path, 'w') as fifo:
    for note in notifications:
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        message = f"[{timestamp}] {note}"
        print(f"[通知] 送信: {message}")
        fifo.write(note + '\n')
        fifo.flush()
        time.sleep(2)

os.remove(fifo_path)
print("[通知] パイプを削除しました。")
listener.py
import os
from datetime import datetime

fifo_path = '/tmp/notify_pipe'

print("[リスナー] 通知待機中...")

# FIFOが作成されるまで待機
while not os.path.exists(fifo_path):
    pass

with open(fifo_path, 'r') as fifo:
    for line in fifo:
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        print(f"[リスナー] 受信: [{timestamp}] {line.strip()}")

まず、受信側のlistener.pyを実行します。

すると、通知待機状態に入ります。

Pipe % python3 listener.py 
[リスナー] 通知待機中...

続いて、別のターミナルを開き、送信側のnotifier.pyを実行します。

すると、通知処理が開始されます。

Pipe % python3 notifier.py
[通知] 送信: [2025-03-29 20:21:16] サンプル通知スタート
[通知] 送信: [2025-03-29 20:21:18] 通知1
[通知] 送信: [2025-03-29 20:21:20] 通知2
[通知] 送信: [2025-03-29 20:21:22] 通知3
[通知] 送信: [2025-03-29 20:21:24] 通知を終了します。
[通知] パイプを削除しました。

受信側のlistener.pyを実行したターミナルに戻ると、送信側プロセスから送られてきたメッセージを受信していることが分かります。パイプを通じて、別のプロセスからメッセージを受け取れたわけです。

Pipe % python3 listener.py
[リスナー] 通知待機中...
[リスナー] 受信: [2025-03-29 20:21:16] サンプル通知スタート
[リスナー] 受信: [2025-03-29 20:21:18] 通知1
[リスナー] 受信: [2025-03-29 20:21:20] 通知2
[リスナー] 受信: [2025-03-29 20:21:22] 通知3
[リスナー] 受信: [2025-03-29 20:21:24] 通知を終了します。

最後に

以上です。

超基本的な内容ですが、パイプについて学んだことを整理しました。もし何かの理解の助けになれば幸いです。

1

Discussion

ログインするとコメントできます