フリーキーズ | 独学プログラミング

プロセスとスレッドの違いをわかりやすく解説!プログラミング初心者が知るべき基礎知識

リンドくん

リンドくん

たなべ先生、「プロセス」と「スレッド」って何が違うんですか?
プログラムを動かすときによく聞く言葉なんですけど、いつも混乱してしまって...

たなべ

たなべ

確かにこの2つは混同されがちなんだけど、実はまったく異なる概念なんだ。
簡単に言うと、プロセスは「独立した作業現場」で、スレッドは「その現場で働く作業員」みたいなものなんだよ。

プログラミングを学び始めると、必ず耳にする「プロセス」と「スレッド」という用語。
しかし、その違いを正確に理解している人は意外と少ないのではないでしょうか。

この記事では、コンピュータサイエンスの基礎となるプロセスとスレッドの違いについて、初心者の方でもしっかり理解できるよう、できるだけわかりやすく解説していきます。

オンラインコミュニティ運営しています

HackATAは、IT技術を習得する人のために広く開かれたオンラインコミュニティです。 現在、無料コミュニティとして開放していますので、ご気軽に参加してください。

✓ 再立ち上げ

✓ コミュニティの方向性について意見募集中

HackATA公式Webサイト

プロセスとは何か? - 独立した実行単位

リンドくん

リンドくん

そもそもプロセスって、正確にはどういうものなんですか?

たなべ

たなべ

プロセスは実行中のプログラムのことなんだ。
例えば、君がブラウザを開いたり、音楽プレイヤーを起動したりすると、それぞれが別々のプロセスとして動いているんだよ。

プロセスの基本概念

プロセスとは、オペレーティングシステム(OS)によって管理される実行中のプログラムの単位です。
プログラムファイル自体は単なるディスク上のデータですが、それを実行すると「プロセス」として動き出します。

プロセスには以下のような特徴があります。

  • 独立したメモリ空間を持つ - 各プロセスは自分専用のメモリ領域を持ち、他のプロセスからは隔離されています
  • 固有のプロセスID(PID)を持つ - OSがプロセスを識別するための番号が割り当てられます
  • 独自のリソースを管理する - ファイルハンドル、ネットワーク接続などを独自に管理します
  • 他のプロセスと独立して動作する - あるプロセスがクラッシュしても、他のプロセスには影響しません

プロセスのメモリ構造

プロセスが持つメモリ空間は、通常以下のような領域に分かれています。

  • テキスト領域 - プログラムコード(実行可能な命令)
  • データ領域 - グローバル変数や静的変数
  • ヒープ領域 - 動的に確保されるメモリ(malloc、newなどで確保)
  • スタック領域 - 関数呼び出しやローカル変数のための領域

この独立したメモリ空間があることで、プロセス間の安全性が保たれているのです。あるプロセスが暴走しても、他のプロセスのメモリを破壊することはできません。

プロセスの状態遷移

プロセスは実行中に以下のような状態を遷移します。

  • 新規(New) - プロセスが作成された直後
  • 実行可能(Ready) - 実行できる状態だが、CPUの割り当てを待っている
  • 実行中(Running) - CPUで実際に実行されている
  • 待機(Waiting) - 入出力の完了などを待っている
  • 終了(Terminated) - 実行が完了した状態

OSは、複数のプロセスを素早く切り替えながら実行することで、あたかも同時に複数のプログラムが動いているように見せています。この仕組みをマルチタスキングと呼びます。

スレッドとは何か? - プロセス内の実行単位

リンドくん

リンドくん

じゃあスレッドはどう違うんですか?プロセスの中にあるって聞いたことがあるんですけど...

たなべ

たなべ

その通り!スレッドはプロセスの中にある、より小さな実行単位なんだ。
1つのプロセスの中に複数のスレッドを作ることで、同時に複数の処理を進められるんだよ。

スレッドの基本概念

スレッドは、プロセス内で実行される軽量な実行単位です。
1つのプロセスは少なくとも1つのスレッド(メインスレッド)を持ち、必要に応じて追加のスレッドを作成できます。

スレッドの特徴は以下の通りです。

  • プロセスのメモリ空間を共有する - 同じプロセス内のスレッド同士は同じメモリにアクセスできます
  • 軽量で生成・切り替えが高速 - プロセスよりも少ないリソースで動作します
  • 独自のスタックを持つ - 各スレッドは自分専用のスタック領域を持ちます
  • 同じプロセス内のスレッドと協調動作する - データを共有しながら並行処理ができます

スレッドのメモリ構造

同じプロセス内のスレッドは、以下のような関係でメモリを扱います。

共有されるもの

  • テキスト領域(プログラムコード)
  • データ領域(グローバル変数)
  • ヒープ領域(動的メモリ)
  • ファイルハンドルなどのリソース

スレッドごとに独立しているもの

  • スタック領域
  • レジスタの値
  • プログラムカウンタ(実行位置)

このメモリ共有の仕組みにより、スレッド間でデータをやり取りするのが容易になりますが、同時アクセスによる競合に注意が必要です。

なぜスレッドが必要なのか

スレッドが登場した理由は、主に以下の2つです。

  1. 効率的なリソース利用
    プロセスを新たに作成するよりも、スレッドを作成する方がはるかに軽量で高速です。

  2. 並行処理の実現
    例えば、Webブラウザでは、ページの読み込み、画像のダウンロード、JavaScriptの実行などを同時に行う必要があります。これらを別々のスレッドで処理することで、快適な体験が実現されています。

プロセスとスレッドの違いを比較する

リンドくん

リンドくん

なるほど...でも、具体的にどう使い分ければいいんでしょうか?

たなべ

たなべ

使い分けのポイントは、独立性が必要か、協調動作が必要かなんだ。
比較表で見てみようか。

主要な違いの比較表

項目プロセススレッド
メモリ空間独立している共有している
生成コスト高い(数msオーダー)低い(数μsオーダー)
切り替えコスト高い低い
通信方法プロセス間通信(IPC)が必要メモリを直接共有
安全性高い(隔離されている)低い(競合の可能性)
クラッシュの影響他のプロセスに影響なし同じプロセス内の全スレッドが停止
並列性マルチコアCPUで真の並列実行マルチコアCPUで真の並列実行

リソースの観点から見た違い

プロセスの場合

プロセスA          プロセスB
├─ メモリ空間A     ├─ メモリ空間B
├─ ファイルA       ├─ ファイルB
└─ リソースA       └─ リソースB
    ↑                  ↑
完全に独立          完全に独立

スレッドの場合

プロセスA
├─ メモリ空間(共有)
├─ ファイル(共有)
├─ リソース(共有)
└─ スレッド
    ├─ スレッド1(スタック独立)
    ├─ スレッド2(スタック独立)
    └─ スレッド3(スタック独立)

使い分けの指針

プロセスを使うべき場面

  • セキュリティや安定性が重要な場合 - 各処理を完全に隔離したい
  • 完全に独立した処理を行う場合 - 別のアプリケーションとして動作させたい
  • 異なる権限で実行したい場合 - 特権モードと通常モードを分ける

スレッドを使うべき場面

  • データを頻繁に共有する必要がある場合 - 同じデータに複数の処理がアクセス
  • 軽量な並行処理を実現したい場合 - 応答性を高めたい
  • リソースを効率的に使いたい場合 - メモリやCPUを節約したい

プロセスとスレッドのサンプルコード

リンドくん

リンドくん

実際のコードではどう書くんですか?見てみたいです!

たなべ

たなべ

いいね!Pythonで簡単な例を見てみよう。プロセスとスレッドの動作の違いがよくわかるはずだよ。

マルチスレッドのサンプルコード(Python)

import threading
import time

# グローバル変数(スレッド間で共有される)
counter = 0

def worker_thread(thread_id):
    """スレッドで実行される関数"""
    global counter
    
    print(f"スレッド{thread_id}が開始しました")
    
    # 5回カウントアップ
    for i in range(5):
        counter += 1
        print(f"スレッド{thread_id}: counter = {counter}")
        time.sleep(0.1)  # 0.1秒待機
    
    print(f"スレッド{thread_id}が終了しました")

# メインプログラム
print("=== マルチスレッドのデモ ===")

# 2つのスレッドを作成
thread1 = threading.Thread(target=worker_thread, args=(1,))
thread2 = threading.Thread(target=worker_thread, args=(2,))

# スレッドを開始
thread1.start()
thread2.start()

# 両方のスレッドが終了するまで待機
thread1.join()
thread2.join()

print(f"\n最終的なcounterの値: {counter}")
print("すべてのスレッドが終了しました")

実行結果の例

=== マルチスレッドのデモ ===
スレッド1が開始しました
スレッド1: counter = 1
スレッド2が開始しました
スレッド2: counter = 2
スレッド1: counter = 3
スレッド2: counter = 4
スレッド1: counter = 5
スレッド2: counter = 6
スレッド1: counter = 7
スレッド2: counter = 8
スレッド1: counter = 9
スレッド2: counter = 10
スレッド1が終了しました
スレッド2が終了しました

最終的なcounterの値: 10
すべてのスレッドが終了しました

この例では、2つのスレッドが同じメモリ空間(counter変数)を共有しながら並行して動作しています。

マルチプロセスのサンプルコード(Python)

import multiprocessing
import time

# グローバル変数(各プロセスで独立)
counter = 0

def worker_process(process_id):
    """プロセスで実行される関数"""
    global counter
    
    print(f"プロセス{process_id}が開始しました(PID: {multiprocessing.current_process().pid})")
    
    # 5回カウントアップ
    for i in range(5):
        counter += 1
        print(f"プロセス{process_id}: counter = {counter}")
        time.sleep(0.1)
    
    print(f"プロセス{process_id}が終了しました(最終counter: {counter})")

# メインプログラム
if __name__ == '__main__':
    print("=== マルチプロセスのデモ ===")
    
    # 2つのプロセスを作成
    process1 = multiprocessing.Process(target=worker_process, args=(1,))
    process2 = multiprocessing.Process(target=worker_process, args=(2,))
    
    # プロセスを開始
    process1.start()
    process2.start()
    
    # 両方のプロセスが終了するまで待機
    process1.join()
    process2.join()
    
    print(f"\nメインプロセスのcounterの値: {counter}")
    print("すべてのプロセスが終了しました")

実行結果の例

=== マルチプロセスのデモ ===
プロセス1が開始しました(PID: 12345)
プロセス1: counter = 1
プロセス2が開始しました(PID: 12346)
プロセス2: counter = 1
プロセス1: counter = 2
プロセス2: counter = 2
プロセス1: counter = 3
プロセス2: counter = 3
プロセス1: counter = 4
プロセス2: counter = 4
プロセス1: counter = 5
プロセス2: counter = 5
プロセス1が終了しました(最終counter: 5)
プロセス2が終了しました(最終counter: 5)

メインプロセスのcounterの値: 0
すべてのプロセスが終了しました

この例では、各プロセスが独立したメモリ空間を持っているため、counter変数も独立しています。

コードから学ぶ重要なポイント

  1. スレッドはメモリを共有する
    スレッドの例では、両方のスレッドが同じcounter変数を更新し、最終的に10になりました。

  2. プロセスはメモリが独立している
    プロセスの例では、各プロセスが独自のcounterを持ち、それぞれ5まで増えましたが、メインプロセスのcounterは0のままでした。

  3. 実行速度の違い
    スレッドの方がプロセスよりも生成・切り替えが高速なため、軽量な並行処理に適しています。

プロセスとスレッドに関する注意点

リンドくん

リンドくん

並行処理って便利そうですけど、何か気をつけることはありますか?

たなべ

たなべ

並行処理には落とし穴があるんだ。
特にスレッドでは、競合状態デッドロックといった問題が起こりやすいんだよ。

競合状態(Race Condition)

複数のスレッドが同じデータに同時にアクセスすると、予期しない結果が発生することがあります。

import threading

counter = 0

def increment():
    global counter
    for _ in range(100000):
        counter += 1  # この操作は実は複数ステップに分かれている!

# 2つのスレッドで同時に実行
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(f"Counter: {counter}")  # 期待値は200000だが、それより小さい値になることがある

対策 ロック(排他制御)を使う

import threading

counter = 0
lock = threading.Lock()

def increment_safe():
    global counter
    for _ in range(100000):
        with lock:  # ロックを取得
            counter += 1

# これで安全に実行できる

デッドロック(Deadlock)

複数のスレッドが互いにリソースを待ち合ってしまい、進行不能になる状態です。

import threading
import time

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1_func():
    with lock1:
        print("スレッド1: lock1を取得")
        time.sleep(0.1)
        with lock2:  # lock2を待つ
            print("スレッド1: lock2を取得")

def thread2_func():
    with lock2:
        print("スレッド2: lock2を取得")
        time.sleep(0.1)
        with lock1:  # lock1を待つ(デッドロック!)
            print("スレッド2: lock1を取得")

対策 ロックの取得順序を統一する

グローバルインタプリタロック(GIL)

PythonにはGILという仕組みがあり、同時に1つのスレッドしかPythonコードを実行できません。そのため、CPU処理を並列化したい場合は、スレッドではなくプロセスを使う必要があります。

# CPU処理が重い場合はマルチプロセスを使う
import multiprocessing

def heavy_calculation(n):
    result = 0
    for i in range(n):
        result += i ** 2
    return result

with multiprocessing.Pool(4) as pool:
    results = pool.map(heavy_calculation, [1000000, 1000000, 1000000, 1000000])

まとめ

リンドくん

リンドくん

プロセスとスレッドの違い、すごくよくわかりました!
これからプログラムを書くときに意識してみます。

たなべ

たなべ

素晴らしいね!この知識があれば、なぜプログラムが遅いのかとか、どうすれば効率化できるのかとか、より深く考えられるようになるよ。
ぜひ実際に手を動かして試してみてね!

プロセスとスレッドの違いについて、ここまで詳しく解説してきました。
最後に重要なポイントをまとめておきましょう。

プロセスの特徴

  • 独立したメモリ空間を持つ「実行中のプログラム」
  • 安全性と安定性が高い(隔離されている)
  • 生成・切り替えのコストが高い
  • プロセス間通信には特別な仕組みが必要

スレッドの特徴

  • プロセス内の軽量な実行単位
  • メモリを共有するため通信が容易
  • 生成・切り替えが高速
  • 競合状態に注意が必要

使い分けの指針

  • 独立性・安全性が必要 → プロセス
  • データ共有・軽量な並行処理 → スレッド
  • CPU処理の並列化 → プロセス(特にPythonの場合)
  • I/O待ち時間の有効活用 → スレッド

この知識は、システムプログラミングWebアプリケーション開発データ処理など、あらゆる分野で役立ちます。
特に、AI・機械学習の分野では大量のデータを効率的に処理するために、並列処理の理解が不可欠です。

プログラミングは実践が何より大切です。ぜひ今日学んだ知識を活かして、実際にコードを書いてみてください。
最初は難しく感じるかもしれませんが、手を動かしていくうちに必ず理解が深まっていきます!

この記事をシェア

関連するコンテンツ