mei_13のPython講座 ロゴ

【解説】Pythonにおけるマルチスレッドプログラミングの基礎と実践:効率的な並列処理を学ぶ




Pythonにおけるマルチスレッドプログラミングの基礎と実践:効率的な並列処理を学ぶ


Yukiのアイコン
【Yuki】 Hiroki君、こんにちは。今日はPythonの「マルチスレッド」という技術について一緒に学んでいこうと思います……。少し複雑な概念に聞こえるかもしれませんが、プログラミングを効率化するためにはとても大切な技術なんです。


Hirokiのアイコン
【Hiroki】 Yukiさん、よろしくお願いします!マルチスレッドって、名前だけは聞いたことがあります。CPUをフル活用して、複数の仕事を同時に片付けるようなイメージですよね?難しそうですけど、頑張って覚えたいです。


Yukiのアイコン
【Yuki】 そうですね、そのイメージで合っています。ただ、Python特有の事情もあって、実は少し「癖」があるんです……。まずは、基本的な概念から説明していきますね。

1. マルチスレッドとは何か


Yukiのアイコン
【Yuki】 まず、「スレッド(Thread)」というのは、プログラムを実行する際の最小単位のことです。通常、プログラムは上から下へと順番に一つずつ命令をこなしていきます。これを「シングルスレッド」と呼びます。


Hirokiのアイコン
【Hiroki】 一本の道に沿って進むような感じですね。


Yukiのアイコン
【Yuki】 はい、その通りです。対して「マルチスレッド」は、一つのプログラムの中で複数の道(スレッド)を同時に走らせる仕組みのことです。例えば、大きなファイルをダウンロードしながら、同時に画面上のボタンを操作できるようにしたり……そういった「待ち時間」を有効活用するために使われます。


Hirokiのアイコン
【Hiroki】 なるほど。もしシングルスレッドだったら、ダウンロードが終わるまで画面が固まっちゃうってことですか?


Yukiのアイコン
【Yuki】 ええ、そうです。ユーザーからすると、アプリが反応しなくなったように見えて不安になりますよね。そういったことを防ぐために、バックグラウンドで重い処理を走らせるのがマルチスレッドの主な役割なんです。

2. Pythonでスレッドを作ってみる


Yukiのアイコン
【Yuki】 では、実際にPythonでどうやってスレッドを作るのか、簡単なコードを見てみましょう。Pythonには標準で threading というライブラリが用意されています。

import threading
import time

def slow_task(name):
    print(f"{name}:処理を開始します...")
    time.sleep(3)  # 3秒間待機する処理
    print(f"{name}:処理が完了しました!")

# メインの処理
print("プログラムを開始します。")

# スレッドの作成
thread1 = threading.Thread(target=slow_task, args=("スレッドA",))
thread2 = threading.Thread(target=slow_task, args=("スレッドB",))

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

# スレッドが終わるのを待つ
thread1.join()
thread2.join()

print("すべての処理が終了しました。")


Hirokiのアイコン
【Hiroki】 お、これだけでいいんですか? threading.Thread を作って、 start() を呼ぶだけ。


Yukiのアイコン
【Yuki】 そうです。意外とシンプルに書けると思います……。ここで重要なのは start() で処理を開始した直後、メインのプログラムは次の行へ進んでしまうということです。だから、プログラムの最後で join() を呼んで、「スレッドが終わるまで待ってね」と伝えているんです。


Hirokiのアイコン
【Hiroki】 もし join() がなかったらどうなるんですか?


Yukiのアイコン
【Yuki】 その場合は、スレッドが裏で動いている間に、メインの処理が「すべての処理が終了しました」と表示して、先に終わってしまうかもしれません……。それは少し、不格好ですよね。

3. CPUバウンドとI/Oバウンドの違い


Hirokiのアイコン
【Hiroki】 マルチスレッドを使えば、どんな計算も2倍、3倍と速くなるんでしょうか?


Yukiのアイコン
【Yuki】 ……実は、そこがPythonの難しいところなんです。マルチスレッドが効果を発揮するのは、主に「I/Oバウンド(入出力待ち)」と呼ばれる処理です。


Hirokiのアイコン
【Hiroki】 アイオーバウンド……?


Yukiのアイコン
【Yuki】 はい。例えば「インターネットからデータを取ってくる」「ファイルを読み書きする」「データベースの返答を待つ」といった、プログラム自体が計算しているのではなく、「外からの返事を待っている状態」のことです。


Hirokiのアイコン
【Hiroki】 なるほど、待ち時間が多い処理のことですね。


Yukiのアイコン
【Yuki】 一方で、複雑な数学の計算や画像処理など、CPUをずっと使い続ける処理は「CPUバウンド」と呼ばれます。実は、Pythonではマルチスレッドを使っても、CPUバウンドな処理を劇的に速くすることはできないんです……。


Hirokiのアイコン
【Hiroki】 えっ、そうなんですか?せっかく複数のスレッドがあるのに、どうしてですか?

4. Pythonの壁「GIL(グローバル・インタプリタ・ロック)」


Yukiのアイコン
【Yuki】 それは、Python(特に一般的なCPython)には GIL(Global Interpreter Lock) という仕組みがあるからです。これは、「一度に一つのスレッドしかPythonの命令を実行させない」という強力なロック(鍵)のようなものです。


Hirokiのアイコン
【Hiroki】 ええーっ! じゃあ、スレッドをたくさん作っても、実際には順番にしか動いていないってことですか?


Yukiのアイコン
【Yuki】 残念ながら、その通りなんです……。複数のスレッドがあったとしても、CPUでの計算処理は交代で少しずつ実行されています。ただし、さっき言った「待ち時間(I/Oバウンド)」が発生している間は、そのスレッドは鍵を離してくれるので、別のスレッドが動くことができます。


Hirokiのアイコン
【Hiroki】 だから、待ち時間が多い通信処理などには有効だけど、計算そのものを並列化して速くするのは苦手なんですね。


Yukiのアイコン
【Yuki】 よく理解できましたね、Hiroki君。もし、CPUバウンドな処理を並列化して高速化したい場合は、スレッドではなく「マルチプロセス(multiprocessing)」という、別の仕組みを使う必要があります。でも、今日はまずスレッドの方をしっかり覚えましょう。

5. スレッドセーフと「Lock」の重要性


Yukiのアイコン
【Yuki】 マルチスレッドを使う際に、一番気をつけなければいけないのが「データの共有」です。複数のスレッドが同時に同じ変数を書き換えようとすると、データが壊れてしまうことがあるんです……。これを防ぐために、「Lock(ロック)」という仕組みを使います。


Hirokiのアイコン
【Hiroki】 データが壊れる……。なんだか怖いですね。具体的にどんなことが起きるんですか?


Yukiのアイコン
【Yuki】 例えば、銀行の口座残高を操作するような処理を想像してみてください。

import threading

balance = 100  # 残高100円
lock = threading.Lock()

def deposit(amount):
    global balance
    # ロックを取得する
    with lock:
        current = balance
        # ここで少し時間がかかると想定
        new_balance = current + amount
        balance = new_balance

# 100円預けるスレッドをたくさん作る
threads = []
for i in range(10):
    t = threading.Thread(target=deposit, args=(100,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print(f"最終残高: {balance}円")


Yukiのアイコン
【Yuki】 このコードの with lock: という部分が大切です。これがあるおかげで、あるスレッドが計算している最中は、他のスレッドが balance を触ることができなくなります。


Hirokiのアイコン
【Hiroki】 もしその with lock: がなかったら、複数のスレッドが同時に「今の残高は100円だな」と読み取ってしまって、みんなが100円を足して合計200円になってしまう……みたいな計算ミスが起きる可能性があるんですね。


Yukiのアイコン
【Yuki】 その通りです。これを「競合状態(レースコンディション)」と呼びます。マルチスレッドを扱うときは、常に「今、複数の場所から同じデータを触っていないか?」ということを意識しなければいけません。控えめな性格の私でも、こういう時はしっかり「鍵」をかけて管理しないと、大きなトラブルになってしまいますから……。

6. 実践的な使い方:ThreadPoolExecutor


Hirokiのアイコン
【Hiroki】 スレッドの作り方はわかりました。でも、たくさんのスレッドを手動で start() したり join() したりするのは、ちょっと大変そうですね。


Yukiのアイコン
【Yuki】 そうですね。最近のPythonでは、もっとスマートにスレッドを管理できる concurrent.futures.ThreadPoolExecutor というクラスを使うのが一般的です。これを使うと、スレッドの使い回し(プール)を自動でやってくれます。

from concurrent.futures import ThreadPoolExecutor
import time

def fetch_data(url):
    print(f"{url} のデータを取得中...")
    time.sleep(1)  # ネットワーク通信の代わり
    return f"{url} のデータ完了"

urls = ["site-A.com", "site-B.com", "site-C.com", "site-D.com"]

# 最大3つのスレッドで並列実行
with ThreadPoolExecutor(max_workers=3) as executor:
    results = list(executor.map(fetch_data, urls))

for res in results:
    print(res)


Hirokiのアイコン
【Hiroki】 わあ、こっちの方がずっとスッキリしていますね! max_workers で同時に動く数を制限できるのも便利そうです。


Yukiのアイコン
【Yuki】 そうなんです。スレッドを無限に作ってしまうと、パソコンのメモリをたくさん消費してしまいます。こうして「同時に動く数を決めて、手の空いたスレッドに次の仕事を任せる」というやり方が、とても効率的なんです……。

7. まとめと注意点


Hirokiのアイコン
【Hiroki】 今日はマルチスレッドについてたくさん教えてもらいました。 1. threading モジュールで複数の処理を並列に動かせる。 2. Pythonには GIL があるので、主にI/Oバウンド(待ち時間が多い処理)に有効。 3. Lock を使って、データの整合性を守る必要がある。 4. ThreadPoolExecutor を使うと管理が楽になる。 ……こんな感じでしょうか?


Yukiのアイコン
【Yuki】 完璧なまとめです、Hiroki君。素晴らしいと思います……。 最後に、マルチスレッドを使うときの注意点として、「デバッグが難しくなる」ということを覚えておいてください。シングルスレッドなら順番に実行されるので間違いを見つけやすいですが、スレッドはバラバラのタイミングで動くので、たまにしか起きない不具合が発生したりすることもあります。


Hirokiのアイコン
【Hiroki】 なるほど。便利だけど、慎重に使わないといけない道具なんですね。


Yukiのアイコン
【Yuki】 はい。でも、ネットワーク通信を多用するプログラムや、ユーザーの操作感を高めたいときには必ず役に立ちます。ぜひ、小さなツールを作る際に試してみてくださいね。


Hirokiのアイコン
【Hiroki】 はい!まずは簡単なスクレイピングとか、ファイルのコピーなんかで試してみようと思います。Yukiさん、ありがとうございました!


Yukiのアイコン
【Yuki】 ……どういたしまして。あ、最後にいくつか参考になるリンクを置いておきますね。もしもっと深く知りたくなったら、夜の静かな時間にでも読んでみてください……。その方が、きっと集中できると思いますから。


参考リンク: - Python 3.12 公式ドキュメント: threading --- スレッドベースの並列処理 - Python 3.12 公式ドキュメント: concurrent.futures --- 並列タスク実行 - Real Python: An Intro to Threading in Python (英語)


Yukiのアイコン
【Yuki】 マルチスレッドの基本はここまでです。次は、計算処理を速くするための「マルチプロセス」についても、また別の機会にお話しできれば嬉しいです……。お疲れ様でした。



< ビット演算
コラム一覧に戻る
参照渡し >

この記事では基礎を解説しましたが、実務においては「もっと複雑なデータを扱いたい」「独自のシステムに組み込みたい」といった、個別の課題に直面することも多いはずです。

「自分で書く時間は最小限に抑え、プロの品質でツールを完成させたい」という方は、ぜひ一度ご相談ください。

  • 専門家の知見に基づいた、保守性の高いコード設計
  • AIの専門家による、Gemini API等の最新AIを組み合わせた高度な自動化
  • ChatGPT等が生成したコードのデバッグ・最適化

「教わる」だけでなく「形にする」パートナーとして、フリーランスエンジニアのmei_13が最短ルートでの解決をサポートします。

➡ ココナラで制作・相談を依頼する(見積もり無料)


初心者から始められるPythonレッスン

プログラミング未経験者・初心者歓迎!
月額4,000円で質問し放題!!
● 完全オンライン
● 翌日までには必ず返信
● 挫折しない独自の学習メソッド
● 圧倒的高評価!!
テキストベースで時間を選ばない
● 高品質なサンプルコード
詳細はこちら
興味がある方はまず質問だけでもどうぞ!



AIアシスタント Yuki