スレッドとjoinの仕組みを理解する
🟢 はじめに
このノートでは、Pythonの threading.Thread
を用いて1分ごとにデータを取得する処理を中心に、スレッドを使う場合と使わない場合の違い、変数の扱い、join()
の意味や合流のタイミングについて体系的にまとめる。
スレッドの動作を人の作業にたとえながら直感的に理解できるように構成している。
🧍♂️ スレッドを使わない場合の挙動
⚙️ 動作のイメージ
while True:
fetch()
time.sleep(60)
-
1分ごとにデータを取得するが、取得と取得のあいだは何もせずに寝ている。
-
メインスレッドがこの処理を行うと、他の処理はブロックされる。
🧠 たとえ:1人のシェフ
-
シェフ(メインスレッド)が「1分ごとにオーブンの温度を見に行く」。
-
それ以外の時間も厨房で突っ立って待ってるだけ。
-
他の仕事(料理、接客)は何もできない。
👯♂️ スレッドを使った場合の挙動
⚙️ 動作のイメージ
def fetch_loop():
while True:
fetch()
time.sleep(60)
thread = threading.Thread(target=fetch_loop)
thread.start()
-
別スレッド(分身)で
fetch_loop
が動き始める。 -
メインスレッドは別の処理を並行して実行可能になる。
🧠 たとえ:シェフと助手
-
シェフが料理に集中し、オーブンの温度確認は助手に任せる。
-
二人で作業を分担しており、作業効率が良い。
🧠 スレッドの分身と合流の考え方
✅ 分身(start)
thread = threading.Thread(target=job)
thread.start()
-
start()
した瞬間に分身が出現して別処理を始める。
✅ 合流(join)
thread.join()
-
「このスレッド(分身)が終わるまで待つ」。
-
待ちたい側(通常メインスレッド)が呼ぶ。
-
分身が
join()
を呼んでも意味がなく、デッドロックになる可能性がある。
🧪 スレッドと変数のスコープ
変数の種類 | スレッド間で共有されるか | 備考 |
---|---|---|
グローバル変数 | ✅ 共有される | 同時に書き換えると競合が発生する(ロックが必要) |
ローカル変数 | ❌ 共有されない | 各スレッドごとに独立 |
オブジェクト属性 | ✅ 共有される | listやdictも同様。中身を変えると他スレッドに影響する |
🛡️ 競合を避けるには threading.Lock
を使う。
⏳ join()
の順番と出力の関係
🔍 joinは待ちたい順に呼ばれる
t1.join()
print("t1 終了を確認")
t2.join()
print("t2 終了を確認")
-
これは「t1が終わるまで待つ → 表示 → t2が終わるまで待つ → 表示」の順になる。
-
スレッドの中の
print()
は実行時間が短い順に表示されるが、メインスレッドのprint()
は書かれた順に出る。
🧠 たとえ:3人の配達員
-
Aが3分、Bが2分、Cが1分かかる仕事をしている。
-
あなたはA→B→Cの順に
join()
して待っている。 -
たとえCが1分で終わっても、Aの
join()
が終わらない限りメインスレッドは次に進めない。
🧩 joinの誤解:FIFOになるのでは?
❌
join()
の順がFIFOになるわけではない
✅ スレッドの終了順は処理の内容とタイミングに依存する
join()
はスレッドを開始した順ではなく、待つ順に書くもの。
処理の終了順とは関係ない。
📝 まとめ
-
スレッドを使うと、定期処理を裏で動かしつつ他の処理も行える。
-
start()
で分身を生成、join()
で合流する。 -
join()
は「待つ側が呼ぶ」。 -
メインとスレッドの
print()
出力順は、書かれた場所と処理時間に依存。 -
複数スレッドの
join()
は、順番に待つだけであって終了順とは無関係。