コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB
スレッドとプロセスの違い
別の家に住むと、砂糖を借りるのに玄関から回ることになる
プロセスの壁は安全を作りますが、代償もあります。画像を 4 分割して 4 プロセスで処理し、結果を 1 枚に戻したいとき、隣のプロセスが作った配列を直接読むことはできません。いったん外に出す形式に直して送り、受け取った側で組み立て直す必要があります。壁が厚いほど、協力するための手間が増えます。
家を分けずに済ませたい。同じ家の中に、別々に動く住人を何人か置けないか。それがスレッドです。
同じ家の別の部屋なら、棚に置くだけで渡る
スレッドは 1 つのプロセスの中に複数立ちます。同居人なので、家具は共有です。コードもヒープも同じものを見ていて、片方が書いた値をもう片方がそのまま読めます。渡すのに変換も送信も要らず、変数に代入するだけで済みます。
別々に持つのは、自分がいま何行目を実行しているかという印と、自分専用のスタックだけです。個人のノートは別、置いてある家具は共有、というわけです。
| プロセス | スレッド | |
|---|---|---|
| 住まい | 1 軒ずつ別 | 同じ家の別の部屋 |
| データの受け渡し | いったん外へ出して送る | 同じ変数を読み書きするだけ |
| 増やすときの重さ | 家を 1 軒建てる | 部屋を 1 つ増やす |
| 切り替えの重さ | 住所ごと入れ替わる | 印とノートだけ差し替わる |
| 1 つ落ちたとき | 他の家は無事 | 家ごと倒れる |
切り替えが軽いのは、スレッドの持ち物が少ないからです。同じ家に住んでいる以上、住所の対応表を差し替える必要がありません。次の住人に代わるとき、持ち替えるのは印とノートだけで済みます。
共有が速いぶん、壊れ方も速い
同じ棚に 2 人が同時に手を伸ばせば事故が起きます。counter += 1 は、読み出して、1 足して、書き戻す 3 手です。読み出した直後にもう 1 人が割り込むと、2 人が同じ値を読んで同じ値を書き戻し、増えたはずの 1 が消えます。
Python
counter = 0
def worker():
global counter
for _ in range(100_000):
counter += 1 # 4 人で回すと 400000 にならない厄介なのは、いつも壊れるわけではないことです。タイミングが噛み合ったときだけ、しかも動かすたびに違う結果で壊れます。プロセスなら壁が守ってくれた事故が、同居した瞬間に自分の責任になります。
だからブラウザはタブごとにプロセスを分けます。片方が落ちても巻き込まれず、悪いページが隣のタブのデータに触れないためです。速さが欲しいなら同居、事故を隔離したいなら別の家。この選択が設計の入口になります。
判断の目安ははっきりしています。ファイルのダウンロードやデータベースへの問い合わせのように、大半が待ち時間で埋まる仕事なら同居が有利です。待っている間に別の住人が働けるので、待ち時間がそのまま重なって消えます。画面の操作を受け付けながら裏で集計する、という作りも同じ理由でスレッド向きです。逆に、他人のコードを動かす場所や、1 か所のバグで全体を止めたくない場所は、素直に家を分けます。
なお CPython には 1 プロセス内で同時に 1 スレッドしか Python のコードを実行させない錠前があり、計算を速くしたい用途ではスレッドが効きません。その場合は素直にプロセスを分けます。