3秒でわかる
1 つのプロセスの中で並行して走る処理の単位。メモリを共有したまま同時に動くため、速さと引き換えに競合状態の管理が必要になります。
もう少し詳しく
どういうものか
スレッドは、1 つのプロセスの中で並行して実行される処理の流れです。プロセスがアプリごとに割り当てられた作業場だとすると、スレッドはその作業場で同時に手を動かす作業者にあたります。同じプロセスに属するスレッドはヒープ領域やグローバル変数、開いているファイルを共有し、スタックとレジスタだけを各自で持ちます。
共有するものが多い分、スレッドの生成や切り替えはプロセスより軽く済みます。その代わり、1 つのスレッドが不正なメモリアクセスで落ちるとプロセス全体が巻き添えになります。
なぜ必要か
ファイル読み込みやネットワーク待ちの間、CPU は何もしていません。処理を 1 本の流れしか持たない作りにすると、待ち時間の分だけ丸ごと止まります。スレッドを分ければ、片方が待っている間にもう片方が計算を進められ、複数コアがあれば本当に同時に走ります。Web サーバーがリクエストごとにスレッドを割り当てるのは、遅い 1 件が他の全員を止めないようにするためです。
具体例
Python で 2 つのスレッドが同じカウンタを増やす例です。
import threading
counter = 0
lock = threading.Lock()
def worker():
global counter
for _ in range(100000):
with lock:
counter += 1
threads = [threading.Thread(target=worker) for _ in range(2)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # 200000with lock を外すと、結果は 200000 より小さい数になります。counter += 1 が読み出しと加算と書き戻しの 3 段階に分かれており、その途中で切り替わると片方の加算が上書きで消えるからです。
つまずきやすいところ
「スレッドを増やせば速くなる」と考えて数を増やしすぎると、切り替えのコストが増えて逆に遅くなります。また Python では GIL があるため、純粋な計算を複数スレッドに分けても CPU 時間は増えません。計算を速くしたいならプロセスを分ける、待ち時間を隠したいならスレッドや非同期を使う、という使い分けになります。
複数のロックを取る場合は取得順を全スレッドで統一します。A→B と B→A が混在すると、互いに相手の解放を待つデッドロックになります。
似た用語との違い
| 用語 | メモリ | 切り替えの重さ |
|---|---|---|
| プロセス | 独立 | 重い |
| スレッド | 共有 | 軽い |
| 非同期処理 | 共有(単一スレッド上) | 最も軽い |
覚え方
プロセスは家、スレッドはその家に住む人。冷蔵庫を共有しているので、同時に開けると中身が食い違います。