プロセスとスレッド

回 3-2 道 3 コンピュータの中

プロセスは、動いているプログラム 1 回分のことで、OS から自分だけのメモリ空間をもらいます。スレッドは、1 つのプロセスの中を走る処理の流れで、同じプロセスのスレッドどうしはデータを置くメモリを共有し、スタックと進み具合だけを持ち分けます。そのためスレッドどうしはデータを写さずに渡せる代わりに、同時に書き換えると値がずれ、プロセスどうしは写しを持つので互いの値を書き換えません。

2 つの処理が同じ物を触っているのか、写しを触っているのかを、メモリの持ち方で切り分けられるようになります。

2 つの処理は、同じ物を触っているのか

2 つの処理が同じメモリを触るのか、別々の写しを触るのかで、プロセスとスレッドは分かれます。

ブラウザでタブを 2 つ開き、片方の頁が固まったとします。もう片方のタブが動き続けるか、ブラウザごと止まるかは、仕事を何に分けて、メモリをどう持たせたかで決まります。

まず言葉を分けます。プログラムは、ディスクに置いてある命令の並び (ファイル) です。プロセスは、そのプログラムを動かした 1 回分で、OS (基本ソフト、Windows や macOS や Linux) から番号 (PID) と自分だけのメモリをもらって動きます。同じプログラムからでも、番号の違うプロセスをいくつも動かせます。

スレッドは、1 つのプロセスの中を何本も走らせられる処理の流れです。この回の終わりには、2 つの処理が同じ値を書き換え合うかどうかを、メモリの持ち方から答えられるようになります。

プロセスとスレッドは何が違うのか

プロセスはメモリを自分だけで持ち、スレッドは同じプロセスのメモリを仲間と共有します。違いのほとんどは、この 1 点から出てきます。

プロセスは自分だけのメモリ空間を持つ

プロセスは、OS から自分だけのメモリ空間 (番地の並んだ置き場) をもらいます。別のプロセスのメモリは、ふつうの書き方では見えも書けもしません。

Linux の fork (今のプロセスを複製して子のプロセスを作る呼び出し) の説明書は、親と子は別々のメモリ空間で動き、作った時点では同じ中身で、片方の書き込みはもう片方に影響しないと書いています。同じ説明書は、Linux の fork は書き込みのときに写す (コピーオンライト) 仕組みで作られているとも書いています。書き込まれた所から写しますが、外から見えるふるまいは「全部の写しをもらった」と同じです。

この頁では、写しを copy.deepcopy (中の入れ物まで丸ごと写す Python の道具) で表します。

スレッドはメモリを共有する

スレッドは、同じプロセスのほかのスレッドと、データを置くメモリ (グローバル変数とヒープ) を共有します。Linux の pthreads (スレッドの標準の作り方) の説明書は、スレッドどうしは同じグローバルなメモリを共有し、スタックは 1 本ずつ持つと書いています。

持ち分けるのは、どこまで進んだかの印 (プログラムカウンタ、pc) と、関数の中の一時的な値を積むスタックです。この頁では、共有を「同じ物を指す参照」(別の名前で同じ辞書を指すこと) で表します。Python の公式の文書も、代入は物を写さず、名前と物を結び付けるだけだと書いています。

厨房をプロセス、コックをスレッドにたとえ、同じ棚を同時に書き換えると数がずれることを示す図

違いを表で比べる

比べる所プロセススレッド
データを置くメモリ自分だけで持つ同じプロセスの仲間と共有
スタックと pc自分で持つ1 本ずつ持つ
開いたファイル自分で持つ仲間と共有
データの渡し方写して渡す (プロセス間通信)同じ物を見るだけ
1 つが壊れたときほかのプロセスに広がりにくい共有した値が半端に残る
同時に書く危うさ写しなので起きない (共有メモリを使うと起きる)値がずれる (競合)

pthreads の説明書は、プロセスの番号 (PID) と開いたファイルの番号もスレッドどうしで共有すると書いています。下の図で本数を増やし、単位をスレッドとプロセスで切り替えると、共有される組が 1 個のままか、本数ぶんに増えるかが変わります。

共有するものと持ち分けるもの

切り替えは、決めた刻みで順番に

CPU が同時に進められる流れの数には限りがあるので、OS はそれより多い流れを短い時間ずつ順番に入れ替えて進めます。この入れ替えをコンテキストスイッチ (いまの流れの pc と途中の値をしまい、次の流れの分を戻すこと) と呼びます。

決めた時間の刻みで順番に回すやり方を、ラウンドロビン (持ち回り) と呼びます。スレッドどうしの入れ替えは、同じメモリ空間のまま pc とスタックを入れ替えるだけです。プロセスどうしの入れ替えでは、メモリ空間ごと見る先を替えるので、その分の手間が増えます。fork の説明書も、子を作るときにページの表 (番地とメモリの対応の表) を写す時間とメモリがかかると書いています。

プロセスどうしは、写して渡す

メモリを共有しないプロセスどうしがデータを渡すには、プロセス間通信 (IPC) を使います。ファイル、パイプ (片方が書き、片方が読む管)、ソケット (ネットワークと同じ書き方の管)、共有メモリ (わざと同じ置き場を両方に見せる仕組み) などがあります。

下の図で渡し方を切り替えると、同じデータを渡すのに実際に写す量が変わります。パイプとソケットは書く側と読む側で写し、共有メモリは写さずに同じ置き場を見ます。

渡し方で、運ぶ量が変わる

動かして確かめる

メモリの持ち方の違いは、写しと参照の模型で動かせます。まず、プロセス 2 つには写しを、スレッド 2 本には同じ辞書を渡し、同じ仕事をさせます。

動かす 写しと共有の違い

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

P1 {'n': 1, 'log': ['P1']}
P2 {'n': 1, 'log': ['P2']}
元 {'n': 0, 'log': []}
元 {'n': 2, 'log': ['T1', 'T2']}
True False

プロセスの形では、P1 と P2 がそれぞれ自分の写しで n を 1 にし、元の辞書は n が 0 のままです。スレッドの形では、T1 と T2 が同じ辞書に書くので、n は 2 になり、log には 2 本の名前が並びます。最後の行のとおり、t1 は元と同じ物で、p1 は別の物です。

次に、スレッドが何を持ち分けるかを確かめます。2 本のスレッドに、それぞれの pc とスタックを持たせ、置き場 (heap) は 1 つだけにして、決めた順に 1 歩ずつ進めます。

動かす pc とスタックは持ち分ける

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

T1 pc 3 [1, 2, 3]
T2 pc 1 [1]
heap T1 T2 T1 T1

T1 は 3 歩、T2 は 1 歩進み、pc とスタックはそれぞれの進み具合だけを覚えています。heap は 1 つなので、2 本の書いた跡が順番どおりに混ざって並びます。

最後に、ラウンドロビンの模型です。仕事の量が 5、2、3 の A、B、C を、1 回に 2 ずつ順番に進めます。

動かす ラウンドロビンで回す

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

時刻 2 A 残り 3
時刻 4 B 残り 0
時刻 6 C 残り 1
時刻 8 A 残り 1
時刻 9 C 残り 0
時刻 10 A 残り 0
入れ替え 5 回

走った回は 6 回で、走る仕事が前の回と違ったときだけ数えるので、入れ替えは A から B、B から C、C から A、A から C、C から A の 5 回です。B は 1 回で抜け、残りが刻みより少ない回は残りの分だけ進みます。

共有すると、どこで壊れるか

共有したメモリは、2 本が同時に書くと値がずれ、1 本が途中で止まると半端な値が残ります。写しを持つプロセスでは、どちらも相手に広がりません。

同時に書くと、数がずれる

n += 1 のような 1 行も、中では「読む、足す、書く」の 3 歩に分かれます。2 本のスレッドの歩みが交互に入ると、片方の書き込みが消えます。これを競合状態 (レースコンディション) と呼びます。

動かす 読む、足す、書くの順で変わる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

順に進めた 7
交互に進めた 6

5 に 2 本が 1 ずつ足すので、正しい答えは 7 です。T1 が書き終えてから T2 が読むと 7 になります。2 本とも書く前に 5 を読むと、それぞれ 6 を書き、後から書いた 6 が残って 1 回分が消えます。本物のスレッドでは、どの順で入れ替わるかを OS が決めるので、同じコードでも 7 の回と 6 の回が混ざります。

1 本の失敗が、共有した値を半端に残す

共有したメモリを書きかけで止まると、半端な値がほかのスレッドにも見えたまま残ります。写しで動いていれば、失敗した写しを捨てるだけで元は無傷です。

動かす 書きかけで止まる

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

失敗 在庫切れ
['book', 'pen'] 1200
失敗 在庫切れ
['book'] 1200

スレッドの形では、カートに pen が入ったのに、代金は 1200 のままです。品物と代金が食い違った状態が、共有のメモリに残りました。プロセスの形では、失敗したのは写しなので、元のカートは book だけです。

Chromium (Google Chrome の元になったブラウザ) の設計の記録は、描画の部品を別々のプロセスに分けるのは、1 つの描画の部品の不具合から全体を守るためだと書いています。前の節の表の「1 つが壊れたとき」の行は、この性質のことです。

崩れる前提をまとめると、次の通りです。

  • 自分だけが書いている、という前提。共有したメモリでは、ほかのスレッドも同じ値を読み書きしている
  • 1 行は割り込まれない、という前提。n += 1 は読む、足す、書くに分かれ、間に別の流れが入る
  • 途中で止まっても元に戻る、という前提。共有したメモリには、書きかけの値がそのまま残る

仕事でどう確かめるか

仕事では、共有している値を一覧にし、そこに鍵をかけるか、写して渡すかを決めます。

1 つ目は、共有しているものを数えることです。グローバル変数、クラスの持つ値、キャッシュの辞書など、複数のスレッドから書くものを洗い出します。書くものが無ければ、競合は起きません。

2 つ目は、書く所に鍵 (ロック、1 本ずつしか通さない仕組み) をかけることです。Python なら threading.Lock を使い、読む、足す、書くの 3 歩を 1 本ずつ通します。上の模型でいえば、交互の順 B を、順 A に戻す働きです。

3 つ目は、分け方を選ぶことです。

向く仕事選ぶ形理由
待ちの多い仕事 (通信、ファイル)スレッド同じデータを写さずに使え、待つ間にほかの流れを進められる
壊れても全体を止めたくない仕事プロセス写しなので、壊れても元に広がりにくい
計算の多い Python の仕事プロセスCPython では一度に 1 本のスレッドしか Python のコードを進めない

4 つ目は、手元で写しか共有かを確かめることです。Python なら a is b が True なら同じ物、False なら別の物です。下の模型は、プロセスどうしがファイルを通して写しを渡す流れです。

動かす ファイルで写して渡す

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

渡した 8 バイト
子 {'n': 4}
親 {'n': 3}
False

8 バイトの文字に直してから渡したので、子が受け取ったのは写しです。子が n を 4 にしても、親は 3 のままです。

頁の中の Python ではスレッドもプロセスも作れません。手元の Python 3.14 で、同じ仕事をスレッドとプロセスで 1 回ずつ動かした結果は次の通りです。

スレッドとプロセスで同じ仕事

import threading
import multiprocessing as mp

box = []

def work():
    box.append("書いた")

if __name__ == "__main__":
    t = threading.Thread(
        target=work)
    t.start()
    t.join()
    print("スレッドの後", box)
    box.clear()
    p = mp.Process(target=work)
    p.start()
    p.join()
    print("プロセスの後", box)

手元の Python 3.14 で動かした結果

スレッドの後 ['書いた']
プロセスの後 []

スレッドが書いた跡は親から見え、プロセスが書いた跡は子の写しに残るだけで、親の box は空のままです。

関わる実例

共有したメモリと切り替えの順番は、火星の探査機でも問題を起こしました。

Mars Pathfinder の再起動 (1997 年)

1997 年、火星の上で動いていた Mars Pathfinder の計算機が、全体の再起動 (リセット) を起こしました。ソフトウェアの責任者だった JPL の Glenn Reeves が 1997 年 12 月 15 日に書いた記録によると、探査機の仕事は VxWorks (組み込み機器向けの OS) のタスクに分かれ、多くのタスクは 1 つの共有メモリの置き場を通して最新のデータを受け取っていました。気象を測る ASI/MET のタスクだけはパイプ (プロセス間通信の仕組み) でデータを受け取り、その待ち合わせ (select) の中の名簿を、排他用のセマフォ (鍵) が守っていました。

優先度の低い ASI/MET のタスクが、この鍵を返し終える前に、中くらいの優先度のタスクたちに割り込まれて進めなくなりました。そこへ優先度の高い bc_dist のタスクが同じ鍵を待って止まり、次の周期を準備する bc_sched のタスクが「bc_dist が周期の中で終わっていない」と見つけて、計算機を再起動しました。Reeves はこれを優先度の逆転と呼び、鍵を作るときの設定で優先度の継承 (鍵を持つ低いタスクに、待つ高いタスクの優先度を一時的に渡す) を有効にして直したと書いています。

崩れた前提は「鍵で守れば、共有メモリの順番の問題は片づく」です。鍵は書き換えの重なりを防ぎますが、どの順で入れ替えるかとの組み合わせで、新しい止まり方を生みました。

Chromium の複数プロセスの設計

Chromium の設計の記録は、描画の部品が落ちたり固まったりしないように作るのはほとんど無理だと書き、描画の部品をタブなどの単位で別々のプロセスに分けています。描画のプロセスが落ちると、そのタブには落ちたことを知らせる絵が出て、読み込み直すと新しいプロセスが作られます。

ここで外した前提は「1 つのプロセスの中なら、部品はみな正しく動く」です。

よくある問い

パソコンのプロセスとは何ですか

パソコンのプロセスは、いま動いているプログラム 1 回分のことです。Windows のタスクマネージャーや macOS のアクティビティモニタに並ぶ 1 行ずつが、プロセスに当たります。同じアプリを 2 つ開くと、番号の違うプロセスが 2 つ並ぶことがあります。

スレッドとタスクの違いは何ですか

タスクは「ひとまとまりの仕事」を指す広い言葉で、OS や道具によって指す物が違います。Mars Pathfinder の記録では、VxWorks のタスクどうしが共有メモリの置き場を通してデータを受け渡していました。言葉に迷ったら、その仕事がほかの仕事とメモリを共有するかどうかで、プロセスに近いかスレッドに近いかを見分けます。

CPU のスレッドとは何ですか

CPU の仕様に書かれるスレッドの数は、CPU が同時に抱えられる命令の流れの数を指し、この回の OS のスレッドとは別の物です。OS は、スレッドをその流れに入れ替えながら載せて進めます。

Python のスレッドとプロセスはどう使い分けますか

Python の公式の文書は、CPython ではグローバルインタープリタロック (GIL) のために、一度に 1 本のスレッドしか Python のコードを進められないと書いています。計算の多い仕事を複数のコアで速くしたいなら multiprocessing (プロセスで分ける道具) を勧めています。通信やファイルの待ちが多い仕事なら、スレッドでも待つ間にほかの流れを進められます。

確認クイズ

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。4 問すべてに正しく答えると、この回は修了で、石が緑に変わります (この端末のブラウザに残ります)。間違えた問いは、あとから解き直せます。

1 問目

プロセスとスレッドの違いを、メモリの持ち方で言ったものはどれですか。

2 問目

このコードを動かすと何が出ますか。

写しと参照

import copy
a = {"x": [1]}
b = a
c = copy.deepcopy(a)
b["x"].append(2)
print(len(a["x"]), len(c["x"]))

3 問目

共有の値 5 に、2 本のスレッドが「読む、足す、書く」で 1 ずつ足します。2 本とも書く前に 5 を読んだとき、最後の値はいくつですか。

4 問目

ラウンドロビンについて正しいものはどれですか。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書いたら、下の模範解答を開いて比べてください。

  • プロセスとスレッドの違いを、メモリの持ち方で
  • その違いから、スレッドで気を付けることと、プロセスで払う手間を

模範解答

自分の説明を書いてから開いて、比べてください。

この回の先の一歩

Web のサーバーを何個のプロセスと何本のスレッドで動かすかを決めるとき、共有するデータに鍵をかけるか写して渡すかを選ぶとき、この回のメモリの持ち方が判断の土台になります。

もっと深く

この回の続きを、ゆめさくのコースの章や道具で深められます。頁の中の確認クイズに答えたあと、手を動かす量を増やしたいときに使ってください。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

道 3 コンピュータの中の回

  1. 3-1OS の役目とシステムコールOS が間に立つ理由頁で読む
  2. 3-2プロセスとスレッドスケジューラ、プロセス間通信この回
  3. 3-3メモリの階層とキャッシュ局所性、当たりと外れ、L1 から主記憶まで頁で読む
  4. 3-4取り合いと排他レースコンディション、Mutex頁で読む
  5. 3-5CPU が命令を実行する命令、レジスタ、クロック頁で読む

道 3 の頁へ戻るほかの道を見る