プロセス間通信(Inter process communication)
マルチプロセスについて
複数のプロセスが、ある共有メモリ領域を競合するアクセスをしている時、競合アクセスしているプログラム部分をクリティカルセクションと言う。
ある資源を同時に利用できるスレッド数が最大1の場合を相互排除と呼ぶ。
※スレッド コンピュータのプログラムは、基本的に 1 行ずつコードが実行されながら動作する。通常、分岐やループがあっても、プログラム全体は 1 つの流れになっている。このような一連のプログラムの流れを「スレッド」(Thread:「糸」などの意味)と呼ぶ。
Linux のプロセス間通信
プロセス間通信とはプロセスの間で行われるデータ交換のこと。
インターネット通信など。
共有メモリ
一つの CP 内でメモリを共有することで同時に読み出しできるためアクセススピードが高速になる。同時に書き込むと競合が発生する。
セマフォ
整数型のデータを親子関係のないプロセス間で共有できる。共有メモリの相互排除に使われる。
マップドメモリー
複数のプロセスがファイルを介してデータを交換する。高速化のためにメモリ上にあるように仮想アドレス空間を用いる。
パイプ
親子関係にあるプロセス間の一方向の通信を実現。
linux、unix 系統などでは小さくて汎用なプログラムを複数繋ぐことで大きなプログラムを実装する思想があるよう。
名前付きパイプ
親子関係がなくても全てのプロセスが FIFO を作成、アクセス、削除できる。
ソケット通信
依存関係のないプロセス間での通信を実現。歴史的には他のマシンにあるプロセスと通信するため名前付きパイプの拡張であるため、他のマシンにあるプロセスと通信できる。
排他制御
相互排除の状態をどうやって実現するか?
Peterson アルゴリズム
プロセス A と B の2つのプロセスがあるとする。
下記の図はプロセス B から見たプロセスを示すもの
3番目のプロセスの繰り返し確認をするというのは1ユニットが短いものであれば効率がいい場合もあるが、何度も確認するビジーウェイト(スピンロック)状態になり CPU がアイドル状態にならず効率が悪い。
セマフォ(オランダ語で信号)
資源が何個使用可能かを記録する方式
バイナリセマフォの例
初期値は同時に使用可能なプロセス数。P 操作でセマフォを1減ずる。
P 操作後クリティカルセクションが実行される。セマフォが0を下回る場合はブロックされ他のプロセスにプロセッサを譲る。ブロックされたプロセスは FIFO の待ち行列に並ぶ。
V 操作で瞬間的にセマフォに1が加えられプロセス B が再開されクリティカルセッションが実行される。P 操作と V 操作は1セットで両方不可欠。
デッドロック
下記の図のように2つのプロセスで要求しているものが割り当てられずに処理が先に進まず膠着状態になっていること。
デッドロック解決法
防止
確保待ちにしない: 必要な資源の要求を一括して要求する。
循環待ちにしない: リソースに対して順序づけを行い、その順序に従い要求する。
回避
銀行家のアルゴリズム: 資源要求を満たしてもデッドロックが発生しない場合のみ割当。
全てのプロセスが要求する可能性がある資源量、各プロセスが占有している資源量、システムに存在する資源量を把握する必要があるため現実的ではない。
回復
- デッドロックを検出し解消さる方策。循環を待ちを解消する
- 強制終了させる
- ロールバックさせる ->チェックポイントリスタート
- 同じ処理を同じ時間にしなおすとまたデッドロックが発生してしまうため時間をランダムにするなどしてずらす
シグナル
シグナルというものを OS から送ってプロセスを中断し何らかの処理を実行する。通常はデフォルトシグナルハンドラが設定されていてプロセスを強制終了する処理をしている。独自のハンドラを設定することもできる。
ソケット通信
ネットワークを経由して通信することを目的としたプロセス間通信
インターネットは TCP/IP と呼ぶ通信プロトコルを利用するがプログラムから利用するにはプログラムと TCP/IP の世界を結ぶ出入り口が必要になる。それがソケット(Socket)である。
ソケットと INET のコンビによって世界中どんなマシンとも簡単に通信できるようになった。
コード
# クライアント側
# create an INET, STREAMing socket
# AF_INET - プロトコルファミリーの一種。ネットワーク経由で別のマシンと通信が可能
# TCPを用いる時はAF_INET, socket.SOCK_STREAMのオプションらしい
import socket
PORT = 50000
BUFFER_SIZE = 1024
# ソケット作成
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# 接続要求
s.connect(('127.0.0.1', PORT))
data = input('Please input > ')
# データを送信し受信したものをdecodeし表示
s.send(data.encode())
print(s.recv(BUFFER_SIZE).decode())
# サーバー側
#「サーバーソケット」ができて、データの送受信を行いcloseする
import socket
サーバ側の待受ポート番号を定義し、ソケットから読み取る際のバッファサイズを指定
PORT = 50000
# データを処理するために一時的に記憶する領域
BUFFER_SIZE = 1024
# ソケットを作成
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# portをバインド
s.bind(('127.0.0.1', PORT))
# 通信を待ち受けている状態
s.listen()
while True:
# 接続
(connection, client) = s.accept()
try:
print('Client connected', client)
# データを受信し大文字にした上で返信
data = connection.recv(BUFFER_SIZE)
connection.send(data.upper())
finally:
connection.close()
参考文献
「分かりそう」で「分からない」でも「分かった」気になれる IT 用語辞典