スマホを使いながら音楽を聞いて、同時にメッセージアプリで友達とやり取りする。パソコンでブラウザを開きながらWordで文書を作成し、バックグラウンドでアプリの更新が進んでいる。こんな「同時作業」は今や当たり前ですが、なぜコンピュータは複数のプログラムを同時に動かすことができるのでしょうか?
実は、この背景には「プロセス」と「スレッド」という巧妙な仕組みがあります。一見すると複数のプログラムが同時に動いているように見えますが、その裏側では想像以上に複雑で興味深い管理システムが働いています。
📌 忙しい人はここだけ読めばOK!
プロセスとスレッドの本質は以下の通りです:
- プロセス → 実行中のプログラム1つ分、独立したメモリ空間を持つ「個別の作業場」
- スレッド → プロセス内部の作業単位、同じメモリを共有する「作業チーム」
- 並行処理 → CPUが高速に切り替えることで、複数の作業が同時進行しているように見せる技術
- コンテキストスイッチ → OSが作業を切り替える際の「作業台の片付け&準備」
身近な例:料理人(CPU)が複数の料理(プロセス)を、各料理の調理工程(スレッド)を細かく切り替えながら同時進行で作っている状態
今すぐできること:タスクマネージャー(Windows)やアクティビティモニタ(Mac)を開いて、実際に動いているプロセス数を確認してみる
プロセスとスレッドの詳細な仕組みや、なぜこの技術が生まれたかを知りたい方は、以下をお読みください。
なぜ「並行処理」という概念が生まれたのか?
この技術の背景を理解するために、コンピュータの歴史を振り返ってみましょう。
初期のコンピュータの制限
1940年代から1960年代のコンピュータは、一度に1つのプログラムしか実行できませんでした。例えば、給与計算プログラムを実行している間は、他の作業は一切できない状況でした。
これは、現代で言えば「スマホで音楽を聞いている間は、メッセージアプリを一切使えない」ような制限です。当時のプログラマーや利用者にとって、これは大きな不便でした。
マルチタスキングの必要性
1960年代になると、コンピュータの処理能力が向上し、複数のユーザーが同じコンピュータを共有する「タイムシェアリングシステム」が求められるようになりました。
例えば、大学の研究室で1台の大型コンピュータを複数の研究者が使用する場合、一人が計算をしている間、他の人は待っているしかない状況は非効率的でした。
時分割システムの誕生
そこで開発されたのが「時分割(タイムシェアリング)」という技術です。これは、CPUの処理時間を細かく分割し、複数のプログラムに順番に割り当てる方法です。
人間の感覚では理解困難ですが、現代のCPUは1秒間に数十億回の処理ができるため、数ミリ秒ずつ作業を切り替えても、ユーザーには「同時に動いている」ように感じられます。
プロセスとは何か?
プロセスの基本概念
プロセスとは、「実行中のプログラム」のことです。同じプログラムでも、実行するたびに別々のプロセスが作られます。
身近な例で説明すると:
- プログラム → 料理のレシピ
- プロセス → そのレシピを使って実際に料理している状況
同じレシピ(プログラム)を使って、複数人が同時に料理(プロセス)を作ることができるのと同じです。
プロセスの独立したメモリ空間
各プロセスは、独立したメモリ空間を持ちます。これは、料理で言えば「それぞれが専用の調理台を持っている」状況です。
具体的には:
- コード領域:プログラムの命令が保存される場所
- データ領域:変数やオブジェクトが保存される場所
- スタック領域:関数呼び出しの情報が保存される場所
- ヒープ領域:動的に確保されるメモリの場所
この独立性により、あるプロセスがクラッシュしても、他のプロセスには影響しません。ブラウザが固まってもメモ帳は正常に動作するのは、この仕組みのおかげです。
プロセスの状態遷移
プロセスは以下の状態を循環します:
- 実行状態:CPUを使って実際に処理を行っている
- 待機状態:CPUの割り当てを待っている
- ブロック状態:入出力処理の完了を待っている
料理で例えると:
- 実行状態 → 実際に野菜を切っている
- 待機状態 → 他の人がコンロを使い終わるのを待っている
- ブロック状態 → オーブンでの加熱が終わるのを待っている
スレッドとは何か?
スレッドの基本概念
スレッドは、プロセス内部の「実行の流れ」の単位です。1つのプロセスは複数のスレッドを持つことができ、これらのスレッドは同じメモリ空間を共有します。
料理で例えると:
- プロセス → 1つの料理を作るチーム全体
- スレッド → チーム内の各担当者(野菜担当、肉担当、調味料担当など)
同じ調理台(メモリ)を共有しながら、それぞれが異なる作業を並行して進めるイメージです。
マルチスレッドの利点
なぜマルチスレッドが有効なのか?
現代のプログラムでは、以下のような並行作業が頻繁に発生します:
- ユーザーインターフェース:ボタンのクリックに応答
- データ処理:大量のデータを計算
- ネットワーク通信:サーバーからデータを取得
- ファイル読み込み:ディスクからファイルを読み込み
これらを1つのスレッドで処理すると、データ処理に時間がかかっている間、ユーザーのボタンクリックに応答できません。マルチスレッドなら、処理用スレッドとUI用スレッドを分けることで、両方を並行して処理できます。
スレッド間の協調と問題
スレッドは同じメモリを共有するため、「競合状態(race condition)」という問題が発生する可能性があります。
料理での例:
共有のメモ用紙に「使った塩の量」を記録しているとします。2人が同時に以下の作業をする場合を考えてみましょう:
- メモを見る(「これまで5g使用」と書いてある)
- 自分が3g使う
- メモを更新する(「5+3=8g使用」と書く)
もし2人が全く同じタイミングで作業すると:
- Aさん:メモを見る(5g)→ 3g使用 → 「8g使用」と書く
- Bさん:メモを見る(5g)→ 2g使用 → 「7g使用」と書く
実際には5+3+2=10g使ったのに、最後にメモを書いた人の値(7g)だけが残ってしまいます。
プログラムでも同様に、複数のスレッドが同じ変数を同時に変更しようとすると、一方の変更が消えてしまうことがあります。
この問題を解決するために、「同期の仕組み」が用意されています。これは「メモを更新している間は他の人は待つ」というルールのようなものです。
コンテキストスイッチ:作業の切り替え技術
コンテキストスイッチの詳細な流れ
コンテキストスイッチとは、CPUが実行するプロセス(またはスレッド)を切り替える処理のことです。
学習机での勉強に例えて、詳しく見てみましょう:
数学の勉強中に英語の勉強に切り替える場合:
- 保存段階:
- 数学の教科書の何ページを開いていたかメモ
- 今解いている問題番号をメモ
- 使っている計算用紙の状態を保存
- 使っていた筆記用具をまとめる
- 切り替え段階:
- 数学の道具を片付ける
- 英語の教科書、ノート、辞書を取り出す
- 机の上を英語学習用に準備
- 復元段階:
- 前回の英語学習のメモを確認
- 中断していたページを開く
- 前回の続きから学習を再開
CPUでも同様に、以下の情報を保存・復元します:
- プログラムカウンタ:「教科書の何ページ」に相当
- レジスタの値:「計算途中の数値」に相当
- スタックの状態:「使っている計算用紙」に相当
- メモリ情報:「どの参考書を使っているか」に相当
重要なのは、この切り替えが1秒間に数千回も行われているということです!人間には不可能な速度ですが、CPUなら可能なのです。
具体的な切り替え処理
CPUレベルでは、以下の情報を保存・復元します:
- プログラムカウンタ:次に実行する命令の位置
- レジスタ:CPUが作業中に使っている一時的なデータ
- スタックポインタ:関数呼び出しの管理情報
- メモリ管理情報:そのプロセスが使っているメモリの場所
これらの情報は「プロセス制御ブロック(PCB)」という特別な領域に保存されます。
切り替えの頻度と効率化の効果
現代のOSでは、1秒間に数千回から数万回のコンテキストスイッチが発生します。この切り替え処理自体にも時間がかかるため、「オーバーヘッド」(余分な処理時間)が発生します。
なぜ切り替えコストを払ってでも並行処理をするのか?
料理での例で考えてみましょう:
- 順番に料理する場合:カレー(60分)→ サラダ(10分)→ スープ(20分)= 合計90分
- 並行して料理する場合:
- カレーの野菜を切る(10分)
- カレーを煮込み始める(煮込み中にサラダ作成10分)
- 煮込み中にスープ作成(20分)
- カレーの仕上げ(10分)
- 合計:約50分で完成!
コンピュータでも同様に:
- ファイル読み込み待機中 → 他のプログラムが計算処理
- ネットワーク通信待機中 → ユーザーインターフェースが応答
- データベース検索中 → 画面描画が並行して実行
切り替えに少し時間がかかっても、待機時間を有効活用できるため、全体としては大幅な効率化が実現できるのです。
実際のプログラムでの活用例
Webブラウザでの例
現代のWebブラウザは、マルチプロセス・マルチスレッド技術を活用しています:
- タブごとに別プロセス:1つのタブがクラッシュしても他のタブは影響を受けない
- UI スレッド:ユーザーの操作に応答
- ネットワークスレッド:Webページのデータをダウンロード
- レンダリングスレッド:HTMLとCSSを画面に描画
- JavaScript実行スレッド:Webページの動的な処理を実行
ゲームプログラムでの例
現代のゲームでは、以下のような並行処理が行われています:
- ゲーム ロジック スレッド:キャラクターの動き、当たり判定の計算
- 描画スレッド:3Dグラフィックスの描画
- 音声スレッド:BGMや効果音の再生
- ネットワークスレッド:オンライン対戦での通信
- AI スレッド:敵キャラクターの行動決定
サーバーアプリケーションでの例
Webサーバーでは、同時に数千人のユーザーからアクセスを受け付ける必要があります:
- リクエスト受付スレッド:新しいアクセスを受け付け
- 処理スレッドプール:実際のリクエスト処理(複数のスレッドで並行処理)
- データベースアクセススレッド:データの読み書き
- ログ出力スレッド:アクセスログの記録
CPUコアとの関係:真の並行処理
シングルコアでの並行処理
1つのCPUコアでは、実際には1つのスレッドしか実行できません。しかし、高速に切り替えることで「並行処理」を実現しています。これを「並行性(concurrency)」と呼びます。
より高度な並列処理技術
複数のCPUコアがある場合、本当に同時に複数のスレッドを実行できます。これを「並列性(parallelism)」と呼びます。
現代のスマホやパソコンは、通常4個から8個以上のCPUコアを持っているため、真の並列処理が可能です。
さらに、「1つのコアで2つの作業を同時に行う技術」も存在します。これは、料理人1人が両手を使って2つの作業を同時に行うようなイメージです。完全に独立した2人分の作業はできませんが、効率は向上します。
OSのスケジューリング:どの作業を優先するか
スケジューリングアルゴリズム
OSは、どのプロセス・スレッドにCPU時間を割り当てるかを決める「スケジューラ」を持っています。主なアルゴリズムには以下があります:
- ラウンドロビン:全てのプロセスに平等に時間を割り当て
- 優先度ベース:重要度の高いプロセスを優先
- CFS(Completely Fair Scheduler):Linuxで使われる公平な割り当て方式
リアルタイム処理
音楽再生や動画再生では、「リアルタイム性」が重要です。音飛びや映像の遅延は、ユーザー体験を大きく損ないます。
OSは、こうした時間に敏感なプロセスに高い優先度を与え、確実にCPU時間を割り当てる仕組みを持っています。
プログラミングでの実践:Javaでのマルチスレッド
Javaでのスレッド作成と実行の流れ
Javaでは、以下のようにマルチスレッドプログラムを作成できます:
// スレッドを作成する基本的な方法
public class MyThread extends Thread {
private String threadName;
public MyThread(String name) {
this.threadName = name;
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ": " + i + "回目の処理");
try {
Thread.sleep(1000); // 1秒待機
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(threadName + ": 処理完了!");
}
}
// メインメソッドでスレッドを起動
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread("スレッドA");
MyThread thread2 = new MyThread("スレッドB");
System.out.println("スレッドを開始します");
thread1.start(); // スレッド1を開始
thread2.start(); // スレッド2を開始
System.out.println("メインスレッドも並行して動作中...");
}
}
このプログラムを実行すると、以下のような出力になります:
スレッドを開始します
メインスレッドも並行して動作中...
スレッドA: 1回目の処理
スレッドB: 1回目の処理
スレッドA: 2回目の処理
スレッドB: 2回目の処理
スレッドA: 3回目の処理
スレッドB: 3回目の処理
スレッドA: 4回目の処理
スレッドB: 4回目の処理
スレッドA: 5回目の処理
スレッドB: 5回目の処理
スレッドA: 処理完了!
スレッドB: 処理完了!
重要なポイント:
- 3つのスレッドが並行動作:メイン、スレッドA、スレッドB
- 順序は保証されない:AとBの出力順序は実行のたびに変わる可能性
- 並行実行:各スレッドが1秒待機している間、他のスレッドが処理を進める
同期処理の重要性と実際の動作
共有データを扱う場合は、synchronized キーワードを使用します:
public class Counter {
private int count = 0;
// synchronized により、1つのスレッドが処理中は他は待機
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
// 使用例:複数のスレッドでカウンターを使用
public class CounterTest {
public static void main(String[] args) {
Counter counter = new Counter();
// 2つのスレッドが同じカウンターを使用
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
System.out.println("スレッド1: " + counter.getCount());
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
System.out.println("スレッド2: " + counter.getCount());
}
});
thread1.start();
thread2.start();
}
}
synchronized がある場合の動作:
スレッド1: 1
スレッド1: 2
スレッド2: 3 ← スレッド1が終わってからスレッド2が実行
スレッド2: 4
スレッド1: 5 ← スレッド2が終わってからスレッド1が実行
スレッド1: 6
...
最終結果: 2000(正確)
synchronized がない場合の危険な動作:
スレッド1: 1
スレッド2: 1 ← 同じ値!(競合状態が発生)
スレッド1: 2
スレッド2: 3 ← 本来なら4のはず
スレッド1: 3 ← 本来なら5のはず
...
最終結果: 1800(本来は2000のはず)
なぜこのような違いが生まれるのか?
synchronized がない場合の危険な流れ:
- スレッド1:count の値を読み取る(例:100)
- スレッド2:同時に count の値を読み取る(例:100)← 同じ値!
- スレッド1:100 + 1 = 101 を計算
- スレッド2:100 + 1 = 101 を計算
- スレッド1:count に 101 を書き込み
- スレッド2:count に 101 を書き込み
- 結果:2回増加したのに 101(本来なら102のはず)
synchronized がある場合の安全な流れ:
- スレッド1:increment() メソッドに入る → 「使用中」の札を立てる
- スレッド2:increment() メソッドに入ろうとする → 「使用中」なので待機
- スレッド1:count を読み取り(100)→ 計算(101)→ 書き込み → 「使用中」札を外す
- スレッド2:待機終了 → increment() メソッドに入る → count を読み取り(101)→ 計算(102)→ 書き込み
- 結果:正確に 102
つまり、synchronized は「メソッド使用中は他のスレッドは入室禁止」という看板の役割を果たしているのです!
実際に確認してみよう
今すぐできる実践
- タスクマネージャーの確認(Windows)
- Ctrl + Shift + Esc でタスクマネージャーを開く
- 「プロセス」タブで動作中のプロセス数を確認
- 「パフォーマンス」→「CPU」でコア数とスレッド数を確認
- アクティビティモニタの確認(Mac)
- 「アプリケーション」→「ユーティリティ」→「アクティビティモニタ」
- 「CPU」タブで各プロセスのCPU使用率を確認
- htop コマンド(Linux)
- ターミナルで
htop
を実行 - リアルタイムでプロセスとスレッドの動作を確認
- ターミナルで
興味深い発見
タスクマネージャーやアクティビティモニタを見ると、普段意識していない多くのプロセスが動作していることが分かります。ブラウザだけでも数十個のプロセスが動いていることも珍しくありません!
次回予告:ファイルシステムの世界
今回は、プロセスとスレッドという「プログラムの並行実行」の仕組みを学びました。次回は、コンピュータが「データをどのように整理して保存するか」について深く探っていきます。
ファイルやフォルダという身近な概念の裏側で、どのような巧妙な仕組みが働いているのでしょうか?ハードディスクやSSDの物理的な構造から、効率的なデータ管理の技術まで、段階的に理解していきましょう!
まとめ
プロセスとスレッドは、現代のコンピュータが複数の作業を効率的に処理するための核心技術です:
- プロセス:独立したメモリ空間を持つ実行単位、安全性と安定性を確保
- スレッド:プロセス内の並行実行単位、効率的なリソース活用を実現
- コンテキストスイッチ:高速な作業切り替えによる見かけ上の同時実行
- マルチコア活用:真の並列処理による処理能力の向上
- スケジューリング:公平で効率的なCPU時間の配分
この技術により、私たちは音楽を聞きながらWebブラウジングしたり、複数のアプリケーションを快適に使い分けたりできるようになりました。
一見すると魔法のような「同時処理」も、その裏側には論理的で美しい仕組みがあります。プロセスとスレッドの理解は、より効率的なプログラムを作る第一歩であり、コンピュータという道具をより深く理解する重要な知識です。
あなたのプログラミング学習においても、この並行処理の概念は必ず役立つ日が来るでしょう。今は「なるほど、そういう仕組みだったのか!」という理解で十分です。
📚 他の学習課題も解決しませんか?
この記事は技術的理解カテゴリーの内容でした。プログラミング学習には他にも様々な課題があります:
- 心理的障壁 - 挫折感やモチベーション管理
- 学習プロセス - 効率的な学習方法や継続のコツ
- 実践応用 - より良いコードを書くためのスキル
詳しくはプログラミング学習サポートをご覧ください。
📖 このシリーズの続きを読む
次の記事: 【ゼロから理解するコンピュータ 第10回】ファイルシステム:データを整理して保存する仕組み
コメント