プロセスとスレッド — 作業部屋と、その中で働く作業員

概要 — まず全体をつかむ

初級では「作業部屋(プロセス)と作業員(スレッド)」と捉えました。中級では、独立の安全さと共有の速さのトレードオフ、そして共有ゆえの危うさを見ます。

詳細 — 1段階ずつ追う

これは何をする係?

同じ「同時に処理を進める」でも、プロセスとスレッドではメモリの扱いが正反対です。

  • プロセスは独立 — 互いにメモリが分離されています。他プロセスの領域は見えないので安全ですが、その分、切り替えや情報の受け渡しが重い
  • スレッドは共有 — 同一プロセス内では同じメモリを共有します。データの受け渡しが要らず速いが、同じ場所を同時にいじると競合が起きます。
アニメーション『プロセスとスレッド』を開く
プロセスA独立したメモリ空間スレッド1スレッド2スレッド3共有メモリ(ヒープ・グローバル変数など)↑ 同一プロセス内のスレッドが共有プロセスB独立したメモリ空間スレッド1スレッド2スレッド3共有メモリ(ヒープ・グローバル変数など)↑ 同一プロセス内のスレッドが共有隔離互いに見えないプロセスは独立(メモリを隔離)/スレッドは同一プロセス内でメモリを共有

つまり「分離=安全だが重い/共有=速いが危うい」という対の性質です。

登場人物メモ:

  • ※1 コンテキストスイッチ — CPUが処理対象を素早く切り替える動作。切り替えには手間(コスト)がかかる
  • ※2 排他制御 — 共有データを一度に1つだけが触れるようにする仕組み(ロックなど)
この部分をもっと深く(上級

プロセスが持つ「自分専用のメモリ」は、内部で役割ごとに区画が分かれています。

  • テキスト領域 — 機械語の命令そのもの。書き換え禁止で読むだけ
  • データ領域 — 初期値を持つグローバル変数など(プログラム開始時に確定)
  • ヒープ ※1 — 実行中に必要な分だけ確保する領域。低い番地から上へ伸びる
  • スタック ※2 — 関数呼び出しの記録(戻り先・局所変数)。高い番地から下へ伸びる

ヒープとスタックは互いに向き合って伸び、メモリ の空間を挟み込むように使います。スレッドはこのうち テキスト・データ・ヒープを共有 し、スタックだけを各自持つ ——ここが「速いが危うい」の出発点です。共有部分は受け渡しなしで触れて速い反面、同じ番地を複数のスレッドが同時にいじれてしまうのです。

登場人物メモ:

  • ※1 ヒープ — 実行中に動的に確保・解放するメモリ領域
  • ※2 スタック — 関数の呼び出しと戻りを積み下ろしで管理する領域
  • ※3 TLB — 仮想アドレスから物理アドレスへの変換表のキャッシュ
  • ※4 ミューテックス/セマフォ/条件変数 — 共有資源への出入りを制御する同期の道具
やさしく言うと(初級
  • プロセス実行中のプログラム1つ分です。アプリを起動すると、そのアプリ用のプロセスが生まれます。プロセスは自分専用のメモリ領域を持って動きます。
  • スレッド — プロセスの中で並行して走る、作業の筋です。1つのプロセスの中に、複数のスレッドを持てます。

たとえるなら、1つの作業部屋(プロセス)の中で、複数の作業員(スレッド)が同時に働くイメージです。部屋がプロセス、そこで手分けして働く一人ひとりがスレッドです。

登場人物メモ:

  • ※1 メモリ領域 — プログラムが計算に使う作業机のような場所
  • ※2 並行 — いくつもの作業を、同時進行で進めること

仕事の流れ

  1. CPUは、一度に扱える処理の本数に限りがある
  2. そこでコンテキストスイッチ※1 で、処理対象を高速に切り替える
  3. 人間から見ると、多数の処理が同時に進んでいるように見える
  4. 切り替えの判断(誰に、いつCPUを渡すか)はOSのスケジューラが行う

この「素早く切り替えて同時に見せる」土台は、CPU の動き方そのものです。

この部分をもっと深く(上級
アニメーション『プロセスとスレッド』を開く
プロセスA独立したメモリ空間スレッド1スレッド2スレッド3共有メモリ(ヒープ・グローバル変数など)↑ 同一プロセス内のスレッドが共有プロセスB独立したメモリ空間スレッド1スレッド2スレッド3共有メモリ(ヒープ・グローバル変数など)↑ 同一プロセス内のスレッドが共有隔離互いに見えないプロセスは独立(メモリを隔離)/スレッドは同一プロセス内でメモリを共有

CPUが処理対象を切り替えるとき、水面下では次が起きます。

  1. レジスタの退避 — 実行中のスレッドのCPUレジスタ(途中経過)をメモリへ保存する
  2. 次の状態の復元 — 切り替え先のレジスタを積み直す
  3. (プロセス切り替えなら)アドレス空間の交換 — ページテーブルを差し替える
  4. これに伴い TLB ※3 が無効化され、変換表を引き直すことになる
  5. さらに キャッシュが冷える — 前のプロセスのデータが追い出され、しばらく遅い

スレッド切り替えは1〜2で済みますが、プロセス切り替えは3〜5の 間接コスト が重い。だから「スレッドは軽い」と言われます。ただし、この直接的な保存時間よりも、キャッシュとTLBが温まり直すまでの見えない遅さの方が、しばしば大きいのです。

やさしく言うと(初級
  1. アプリを起動すると、そのアプリのプロセスが生まれる
  2. プロセスは自分専用のメモリ領域を割り当てられる
  3. プロセスの中で、必要に応じてスレッドが作られる
  4. 複数のスレッドが手分けして並行に働き、処理を進める

並行と並列、そして競合

並行(concurrent)と並列(parallel)は違います。

  • 並行 — 1つのCPUが素早く切り替えて、複数の処理を代わる代わる進める(同時に見えるが、瞬間瞬間は1つ)
  • 並列 — 複数のCPUコアが、本当に同じ瞬間に別々の処理を実行する

なぜ共有メモリは便利で危険なのか。 スレッドが同じメモリを共有できると、データの受け渡しが速く便利です。しかし、2つのスレッドが同じデータを同時に読み書きすると、結果が実行の巡り合わせで変わってしまう——これが競合状態(レースコンディション)です。

防ぐには排他制御(ロック)※2 を使い、共有データを一度に1つのスレッドだけが触れるようにします。ただしロックの掛け方を誤ると、互いに相手のロックを待って止まるデッドロックを招くこともあります。速さと安全は、ここでも綱引きになります。

この部分をもっと深く(上級

デッドロック は、次の4つが すべて揃った ときだけ起きます。

  1. 相互排他 — その資源は同時に1つしか使えない
  2. 保持と待機 — 何かを持ったまま、別の何かを待つ
  3. 横取り不可 — 持っている資源を無理やり奪えない
  4. 循環待ち — AはBを、BはAを……と待ちの輪ができる

裏を返せば、どれか1つを崩せば防げる。「ロックを取る順番を全員で固定する(循環を作らせない)」がよく効くのは、この4番目を壊しているからです。

もう1つの危うさが メモリ可視性 です。あるスレッドが書いた値が、別のスレッドやコアから すぐ見えるとは限りません。各コアはキャッシュを持ち、CPUは命令を並べ替えて先へ進むためです(詳しくはCPUのメモリオーダリング)。だから「共有変数を書いた/読んだ」だけでは足りず、同期プリミティブやメモリバリアで順序と見え方を約束させる 必要があります。競合状態がやっかいなのは、たいてい正しく動いてしまい、ごく稀な巡り合わせでだけ壊れるからです。

やさしく言うと(初級

理由は、同時にいくつもの処理を進めたいからです。

たとえば動画アプリなら、「映像を表示する」「音を鳴らす」「操作を受け付ける」を同時に進めたい。1つの作業員が順番に全部やると、どれかで詰まると全体が止まります。複数の作業員(スレッド)で手分けすれば、1つが忙しくても他が動けます。

また、アプリごとに部屋(プロセス)を分けるのも大事です。部屋が分かれていれば、あるアプリが転んでも、隣のアプリの作業机を巻き込みにくくなります。

こうした部屋の割り当てや作業員の順番決めは、OSとアプリ の「世話役」であるOSが裏で管理しています。

⚠️ うまくいかないとき

  • 競合状態 — 共有データの同時書き換えで、結果が不定になる
  • デッドロック — 複数スレッドが互いのロックを待ち続けて止まる
  • 切り替えコスト過多 — スレッドを増やしすぎ、コンテキストスイッチばかりで遅くなる
  • 1プロセスの巻き添え — 共有メモリゆえ、あるスレッドの暴走が同プロセス全体を壊す

なお、プロセスごとにメモリを分離するこの隔離の考え方は、Linux のコンテナが「プロセスを箱に閉じ込めて独立させる」土台にもつながっています。独立と共有の使い分けは、OSの根っこにある設計思想です。

この部分をもっと深く(上級
  • 偽共有(false sharing) — 別々の変数が同じキャッシュ行に同居し、片方を書くと他方も無効化されて無用に遅くなる
  • ロック順序の不一致 — 取る順番がスレッドごとに違うと、循環待ち=デッドロックへ
  • 可視性の見落とし — 同期なしの共有変数は、別コアから古い値に見えることがある
  • スレッド過多 — 増やすほどコンテキストスイッチとメモリ(各自のスタック)がかさみ、かえって遅くなる
  • メインスレッド専有 — イベントループで重い計算を回すと、待ちの列全体が固まる
やさしく言うと(初級
  • 1つのアプリが固まる — そのプロセスの作業が詰まり、応答しなくなる
  • 作業員の取り合い — スレッドが増えすぎて、かえって切り替えに手間取る
  • 同じ机を同時にいじる — 作業員たちが同じ場所を同時に書き換え、記録が壊れる
  • 部屋を閉じ忘れる — 使い終わったプロセスが残り、メモリを無駄に占有する

中級では、「部屋(プロセス)を分ける安全さ」と「作業員(スレッド)が机を共有する速さ」のトレードオフを詳しく見ます。

関連する知識

理解度チェック

そのまま解けます(成績は保存されません)。無料アカウントを作ると、学習の記録と進捗の山登りが始まります。

1. プロセスとスレッドのメモリの扱いとして正しいのは?

2. 複数のスレッドが同じデータを同時に書き換え、結果が実行の巡り合わせで変わってしまう問題を何という?

3. 「並行(concurrent)」の説明として正しいのは?

4. デッドロックが起きるのはどんなとき?

5. プロセスどうしのメモリが分離されていることの効果として正しいのは?

6. 排他制御(ロック)を使う目的は?

7. CPUが処理対象を素早く切り替える動作を、カタカナで何という?

8. 誰に・いつCPUを渡すかを判断する、OSの中の担当を何という?