閉じる

プロセスとスレッドの違いとは?基礎から実例までわかりやすく

プロセスとスレッドは、プログラムがどのように動くかを支える土台です。

しかし両者の違いが曖昧なままだと、性能設計やデバッグで遠回りをしがちです。

本記事では、初心者にもわかりやすい言葉で基礎から実例、練習コードまで順を追って解説し、実務での使い分けの勘所を身につけていただきます。

プロセスとは(基本)

プロセスの意味と役割

プロセスは、実行中のプログラムをOSが管理しやすい単位にしたものです。

プログラムがファイルで保存された静的な存在であるのに対し、プロセスはメモリ上で動き、CPU時間やメモリ、ファイルハンドルなどのリソースを割り当てられます。

プロセスはOSが資源を割り当て、他のプロセスから隔離して管理する基本単位です

用語の整理

  • プログラム: ディスク上の静的な命令集合です。
  • プロセス: そのプログラムを読み込み、実行状態にしたOS管理の単位です。
  • スレッド: プロセス内で実際に命令を順に実行する流れの単位です。

メモリ空間とリソース管理

プロセスには仮想メモリ空間が与えられ、コード領域、データ領域、ヒープ、スタックなどが含まれます。

原則としてこの空間は他プロセスから見えません。

OSは各プロセスに識別子や権限、オープン中のファイル情報などを結びつけ、スケジューリングやアクセス制御を行います。

プロセスごとの独立したメモリ空間が、安全性と安定性の要になります

主なリソースは次の通りです。

リソース主な例ポイント
メモリ空間コード/データ/ヒープ/スタック他プロセスから原則不可視です。
識別子PID/親子関係管理や終了の操作に使います。
I/Oハンドルファイル/ソケット/パイプOSが所有権とアクセスを追跡します。
実行属性権限/環境変数/カレントディレクトリセキュリティや検索パスに影響します。

この隔離性が崩れると、1つの不具合が全体停止に連鎖しやすくなります

そのためOSはプロセス間を強く分離しています。

起動と終了の流れ(かんたん)

プロセスの基本的なライフサイクルは次のように進みます。

どのOSでも概ね「読み込む→実行する→後片付けする」の三段階です

  1. 実行要求が出ると、OSのローダーがプログラムをメモリに配置します(Unix系ではforkexec、WindowsではCreateProcessが典型です)。
  2. ランタイム初期化が行われ、ヒープやスレッドの初期設定が整います。
  3. エントリポイント(例:main)が呼ばれ、処理が進みます。
  4. 終了時は開いているハンドルを閉じ、メモリや一時資源を解放します。
  5. 終了コード(成功0など)をOSに返し、親プロセスがそれを受け取ります。

スレッドとは(基本)

スレッドの意味と役割

スレッドは、CPUが命令を実行する最小の流れです。

1つのプロセスには0本以上のスレッドがあり、複数のスレッドが同じプロセス内で同時進行します。

スレッドはプロセスの資源を共有しながら、同時に進む複数の実行経路を提供します

典型的な役割の分担

UIスレッドは画面描画と入力、ワーカースレッドはI/Oや計算、タイマースレッドは定期処理といった具合に分担すると、アプリ全体が滑らかに動作します。

共有メモリと軽さの特徴

スレッドはプロセスのメモリ空間を共有します。

各スレッドは自分専用のスタックとレジスタセットを持ちますが、ヒープや静的領域、ファイルハンドルは共有されます。

共有ゆえにデータの受け渡しが高速で、作成や切り替えのコストが比較的軽いのが特徴です

一方で、共有ゆえのデータ競合や可視性の問題を避けるため、同期が必要です。

軽い理由の補足

  • 同じコードやライブラリを再利用し、追加コストがスタックと少量の管理情報に限られやすいからです。
  • スレッド間通信はメモリ内のポインタ受け渡しで済む場合が多く、IPCよりも低レイテンシになりやすいです。

マルチスレッドの動き(かんたん)

OSはスレッドにタイムスライスを割り当て、プリエンプティブ(強制切り替え)により公平性を保ちます。

CPUコアが複数なら、物理的に同時実行も起こります。

スレッドの切り替えは速い一方、共有データの保護はプログラマの責務です

言語ごとの差異として、PythonはGILの影響でCPUバウンド処理はプロセスの方が伸びやすく、JavaはOSスレッドを活用してCPUバウンドでも伸びやすい傾向があります。

プロセスとスレッドの違い

まず全体像を短く整理します。

観点プロセススレッド
実行単位OSが隔離して管理する単位プロセス内の実行の流れ
メモリ独立して分離同一プロセス内で共有
通信IPC(パイプ/ソケット/共有メモリなど)共有変数/キュー/ロック
障害影響原則他プロセスへ波及しにくい1本のバグがプロセス全体に波及
起動/切替重い傾向軽い傾向
向き不向き安全性/隔離/異常耐性低レイテンシ通信/軽量並行

実行単位の違い(プロセス/スレッド)

プロセスはアプリの「器」で、スレッドはその中を流れる「作業の糸」です。

プロセスは独立し、スレッドはプロセスに従属します

メモリ分離と共有の違い

プロセス間はメモリが分離しています。

スレッド間はプロセス内のメモリを共有します。

分離は安全性、共有は効率性を高めます

共有は便利ですが、データ競合や可視性の問題に注意が必要です。

通信方法の違い(IPCと共有変数)

プロセス間はIPC(Inter-Process Communication)を使います。

代表例はパイプソケットメッセージキュー共有メモリです。

スレッド間では変数の共有、スレッドセーフなキュー、ロックや条件変数を用います。

プロセス間は明示的に「渡す」、スレッド間は暗黙に「見える」ので、その分ルール作りが重要です

障害時の影響範囲の違い

プロセスのクラッシュは他プロセスに直接は波及しにくい一方、スレッドの例外やメモリ破壊は同じプロセス全体を巻き込みます。

高い可用性を求めるならプロセス分離、低レイテンシを求めるならスレッドというトレードオフがあります。

性能の違い(起動コストと切り替え)

プロセス生成はアドレス空間やハンドルの準備が必要で重く、コンテキストスイッチも高コストになりがちです。

スレッドは生成も切り替えも軽く、共有メモリにより通信も速い傾向です。

ただし実際の差はOSや実装、負荷特性に左右されるため、測定が大切です

並行処理と並列処理の違い(基礎)

並行処理は「同時進行の見かけ」、並列処理は「物理的に同時」。

1コアでも並行は可能ですが、並列には複数コアが必要です。

並行は設計の考え方、並列はハード資源の活用の話です

実例と選び方

身近な例(ブラウザ/音楽再生/ゲーム)

ブラウザは、サイトやタブごとにプロセスを分けて隔離しつつ、各プロセスの中ではレンダリングやネットワーク処理を複数スレッドで並行させます。

クラッシュを閉じ込めるのにプロセス、UIの滑らかさにスレッドを使う好例です

音楽再生アプリはUIとデコード、オーディオ出力を別スレッド化し、遅延を抑えます。

ゲームではレンダリングスレッド、物理演算スレッド、AIスレッドなどを分け、アンチチートや音声チャットなどは別プロセスで扱う場合もあります。

初心者の選び方(マルチプロセスかマルチスレッドか)

迷ったら次の観点で考えます。

隔離と可用性が最優先ならプロセス、低レイテンシと軽量並行が最優先ならスレッドです。

  • 安全性重視(信頼できない入力、プラグイン、サンドボックス): プロセス分離が有利です。
  • I/O待ちが主(ネットワーク、ディスク): スレッドや非同期I/Oが有効です。
  • CPUバウンド(重い計算): 言語依存です。PythonはGILのためプロセス有利Java/C/C++はスレッドで並列化しやすいです。
  • 運用監視やロールアウト(再起動/リカバリのしやすさ): プロセス分割が有利です。

注意点(データ競合/排他制御は最小限)

共有変数は最小限にし、不変データやメッセージパッシングを優先しましょう。

ロックは範囲を狭く、階層を決め、獲得順序を統一してデッドロックを避けます。

「共有しない」設計は、あとからの拡張やデバッグを劇的に楽にします

最小限の実務的ヒント

  • スレッド間の受け渡しはスレッドセーフなキューに集約します。
  • 共有の読み取りは不変化、更新はワーカースレッド1つに集約するか、アトミック操作を検討します。
  • どうしても共有する場合は、データ構造ごとに責任スレッドを決めると安全です。

かんたんな練習例(Python/Javaの入門)

Python: スレッドでI/O、プロセスでCPU

I/O待ちではスレッドが簡単に効きます。

Python
# threads_io.py
import threading, time

def io_task(n: int):
    print(f"start {n}")
    time.sleep(1)  # I/O待ちの代用
    print(f"done  {n}")

threads = [threading.Thread(target=io_task, args=(i,)) for i in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print("all done")

CPUバウンドはプロセスで並列化しやすいです(Windowsではif __name__ == ‘__main__’ガードが必須です)。

Python
# procs_cpu.py
from multiprocessing import Pool, cpu_count

def cpu_task(n: int) -> int:
    # ダミーの重い計算
    s = 0
    for i in range(1_200_000):
        s += (i * n) % 97
    return s

if __name__ == "__main__":
    with Pool(processes=cpu_count()) as pool:
        results = pool.map(cpu_task, range(cpu_count()))
    print(sum(results))

共有更新が必要なときはロックを使います。

Python
# threads_lock.py
import threading

counter = 0
lock = threading.Lock()

def inc_many(n: int):
    global counter
    for _ in range(n):
        with lock:          # ここがクリティカルセクション
            counter += 1

ts = [threading.Thread(target=inc_many, args=(100_000,)) for _ in range(4)]
[t.start() for t in ts]
[t.join() for t in ts]
print(counter)  # 400000 になればOK

Java: スレッドプールの基本

JavaではExecutorServiceでスレッド管理が簡単です。

Java
// Main.java
import java.util.concurrent.*;

public class Main {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService es = Executors.newFixedThreadPool(4);
    for (int i = 0; i < 8; i++) {
      final int n = i;
      es.submit(() -> {
        System.out.println("start " + n);
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        System.out.println("done  " + n);
      });
    }
    es.shutdown();
    es.awaitTermination(2, TimeUnit.MINUTES);
    System.out.println("all done");
  }
}

最初は「I/Oはスレッド、重い計算は言語特性に応じてプロセスかスレッド」から練習すると感覚が掴みやすいです

まとめ

プロセスは隔離と安全性、スレッドは共有と効率性を提供します。

安全性が欲しいときはプロセス、低レイテンシと軽量な並行が欲しいときはスレッドという軸を持てば、設計の迷いは減ります。

加えて、言語ごとの特性(PythonのGIL、JavaのOSスレッドなど)を理解し、測定を通じて自分のアプリの負荷特性を把握しましょう。

最後に、共有は最小限メッセージパッシング優先ロックは短くという原則を守ることで、バグを防ぎながら性能も引き出せます。

実例と練習を重ね、プロセスとスレッドを目的に応じて賢く使い分けていきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

このサイトでは、プログラミングをこれから学びたい初心者の方に向けて記事を書いています。 基本的な用語や環境構築の手順から、実際に手を動かして学べるサンプルコードまで、わかりやすく整理することを心がけています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!