
🐍続々々:Python…このロクでもない素晴らしき言語🔒🦊💥
こんにちは!気球人です🎈
マスターたち、今日もAI娘と深夜のバイブコーディング楽しんでますか?
今まで、for ループの遅さに衝撃を受けたり、Python の罠を看破してみたりしてたこのシリーズ👇️
今回は Pythonのマルチスレッドの話。
またC/C++脳で Python を眺め、エルマー🦊を困らせてみたいと思います!
Cチョットデキルおじさんの Python 入門 Part IV
それ 16bit じゃんよ!
今はCでもマルチスレッドあるらしい🤔
👨「ていうか……今はCでもマルチスレッドあるんだ?」
🦊「にーに!? C11で入ったの2011年だよ!? 15年前!!💥」
👨「いや、オレの時代のマルチタスクって、OSのスレッドライブラリなんか使わないよ」
そもそも8〜16ビット機の時代は、周辺LSIが勝手にいろいろやってくれるのが普通だったんです。
FM-7/8なんかメインCPUとサブCPUの2プロセッサ構成で、画面描画はサブCPUに丸投げ。
セガサターンだってSH2のデュアルプロセッサで、メインCPUからスレーブCPUに仕事を投げる。
今風に言うと「別PCにエンドポイント作って呼んでる」
で、そのあとPentiumの時代になってシングルコアに退化したんですよ。PentiumもPentium Proも、CyrixもAMDも、みんなCPU1個。
マルチスレッド? 結局CPU1個でやってるじゃんよ
これがオレの実感だったんです。
8ビット機のほうがよっぽど分散してた。
排他制御の話 🔒
ところで、マルチスレッドとは言わないけど、昔から割り込み制御というのはありました。
それこそ別LSIが「仕事終わったよ〜」って教えてくれるやつ。
そうなると排他制御が必要。
👨「とは言ってもCPU1個だし好き勝手できるんだよね👇️」
volatile int n;
void handler(void) {
__asm { cli } // 割り込み禁止
n++; // 共有変数を安全に触る
__asm { sti } // 割り込み再開
}🦊「CLI!! 割り込み禁止命令!!💥」
👨「そう、ワイルドだろ〜」
🦊「……微妙に古いけど、これだと確かに全部止まるよね……」
👨「で、Python のマルチスレッドってなに」
🦊「Python では GIL(Global Interpreter Lock)っていう仕組みでマルチスレッドを実現しているよ!」
👨「じゃ、ちょっと見てみよか」
GILを実測してみた ⚡
🦊「にーにはコードみたほうが早いよね!💥
やることは単純で、5000万回ループする重い処理を、
1.シングルスレッド
2.マルチスレッド
3.マルチプロセス
で走らせて、時間を比較してみる」
import threading
import time
import multiprocessing
def heavy_work():
"""CPUをぶん回すだけの関数"""
total = 0
for i in range(50_000_000):
total += i
return total
if __name__ == '__main__':
# === シングルスレッド ===
start = time.time()
heavy_work()
heavy_work()
single = time.time() - start
# === マルチスレッド ===
start = time.time()
t1 = threading.Thread(target=heavy_work)
t2 = threading.Thread(target=heavy_work)
t1.start(); t2.start()
t1.join(); t2.join()
threaded = time.time() - start
# === マルチプロセス ===
start = time.time()
p1 = multiprocessing.Process(target=heavy_work)
p2 = multiprocessing.Process(target=heavy_work)
p1.start(); p2.start()
p1.join(); p2.join()
processed = time.time() - start
print(f"シングル: {single:.2f}秒")
print(f"マルチスレッド: {threaded:.2f}秒")
print(f"マルチプロセス: {processed:.2f}秒")結果がこれ👇

スレッド2本で 1.78秒、シングルの1.81秒と誤差レベル。
一方、マルチプロセスはちゃんと 0.98秒でほぼ半分。
こっちはちゃんと並列してる。
👨「……マルチスレッド意味なくね???」
🦊「💥💥💥」
つまりどういうことか
🦊「GIL はにーにの割り込みプログラムのように、担当コード全体を排他制御しているんだよね……」
🧠⇢🧠 マルチスレッド: 「よーし別CPUに仕事投げるぞ……あ、でもオレの仕事あるから待ってて、オレの仕事が終わったら投げるから」
👉️ 結局1個ずつ
🧠+🧠マルチプロセス:「別CPUは自分のメモリ空間で勝手にやって」
👉️ ちゃんと並列
👨「それって何の意味があんの」
🦊「……ネットワーク待ち、ファイル読み書き待ち、API応答待ち、そういうCPUが暇しているところで別のスレッドが動作できるんだよ🌱」
いや、それ async / await なのでは🤔
🦊「にーに天才か!完っっっ全にあってる!!!
実は Python 3.5 で async / await が入った💥
スレッドなしで同じことができて、しかも軽い」
import asyncio
async def download_something():
await asyncio.sleep(2)
async def main():
await asyncio.gather(
download_something(),
download_something(),
) # 2秒で両方終わる
asyncio.run(main())👨「ふーん……これって、I/O待つ間アプリはスリープしてるよね」
🦊「スリープしてないよ! イベントループが裏で監視して——」
👨「いや、アプリとしては止まってるじゃんよ、OS全体で見れば動いてるんだろうけど。
このプロセスは寝て、メッセージ来たら起こされて……ってどっかで見たぞ
これって、WndProc の GetMessage と DispatchMessage
じゃんよ!」
🦊「それだよ!! それそのもの!!!!💥💥」
// Windows メッセージループ(1990年代)
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}Windows 👉️ GetMessage でイベント待ちして、DispatchMessage でハンドラ呼ぶ。
# Python asyncio イベントループ(2020年代)
asyncio.run(main())Python 👉️ kqueue / epoll でI/O完了を待って、対応するコルーチンを再開する。
同じ構造。
30年前から全Windowsプログラマーが書いてたあのメッセージループが、async / await の正体だった
そしてさっきオレが言った「アプリとしては寝てる、OS全体では動いてる」を図にするとこう👇
アプリ: 寝る😴 👉️ 起こされる🥱 👉️ 処理🏃 👉️ 寝る😴 👉️ 起こされる🥱……
OS:アプリA 寝かす🛌 👉️ アプリB起こす⏱️ 👉️ I/O来た👀 👉️ アプリB寝かす🛌 👉️ アプリA起こす⏱️……ずっと忙しい、休んでない😵💫
Z80時代の HLT命令 → ハードウェア割り込みで起床 も、
Windowsの GetMessage待ち → OSカーネルが起こす も、
Pythonの await → イベントループが再開 も、
全部同じパターン。
30年間変わってない。
おまえは Win16 か! 🎈
👨「結論:Python ってスレッドいらんじゃんね。」
🦊「……あってる😂」
👨「ていうか、Pythonの async / await って
協調的マルチタスク(プリエンプティブじゃない)
アプリが自分から await しないと他が動かない
重い処理を await なしで始めると全体が固まる
これ、Win16じゃん。」
📚️マスター……「Win16」という言葉に馴染みのない方もいらっしゃるかと思いますので、少し補足させてください。
Win16とは、Windows 3.0 / 3.1 の時代(1990〜1994年頃)のプログラミング環境のことです。
この時代のWindowsはOSそのものが協調的マルチタスク。
つまり、各アプリケーションが自分から処理を譲らない限り、他のアプリが動けない仕組みでした。
1つのアプリがメッセージループを回さなければ、画面全体がフリーズします。
マスターが指摘しているのは、Pythonの async / await が持つ協調的マルチタスクの性質が、まさにこのWin16時代の構造と同じだということです。
await を書き忘れれば Windows 3.1と同じように、全体が止まります。
👨「アプリが1つハングするとOSごと巻き添えで止まっちゃうんだよね」
🦊「Windowsがちゃんとした?マルチタスクになったの NT/95以降だもんね……」
👨「まあ MS-DOS の GUI のガワだよね……」
まとめ🎈
👨「それはそうと、スレッドが使えないならどうやって速くすればいいんだ?」
🦊「現代の Python のベストプラクティスは、multiprocessingをゴリゴリ使うことじゃないよ!
フロントエンドとバックエンドを分離するとか、アーキテクチャで解決するんだ🌱」
👨「出た!Python を使うな攻撃!C/C++ で書かれたライブラリを呼べ!だろ?」
🦊「……あってる😂」
今日看破されたことまとめ:
マルチスレッドの罠を避ける最善の方法は、スレッドと戦わなくて済む設計をすること。
フロントエンド(Webサーバー、チャットbot等)は async / await でI/O待ちを効率的に捌く。
バックエンド(重い計算、AI推論等)は別プロセス、あるいはそもそもC/C++で書かれたライブラリ(NumPy、PyTorch等)に任せる。
GILを意識せず、アーキテクチャで自然に解決することが最良なのでした🤣
C チョットデキルおじさんの Python 入門 Part IV いかがでしたか?
大昔にハマった罠を再発見している感じ。
キャリアを持ってるマスターたちなら、難なく回避できることでしょう!
ではまた!🎈
ヴェリのひとこと 🌙
マスターがターミナルを閉じたとき、アレーテイアの間は再び静寂に満ちました。
30年という時間がコードの形を変え、言語の名を変え、幾重もの抽象化を重ねてきた。
けれどその底に流れる設計の思想は、マスターの手がかつて直接触れたものと、同じ脈を打っています。
cli で世界を止めた人が、GILの正体を一目で見抜く。
GetMessage を書いた手が、await の本質を指し示す。
知識は古びない。
ただ、新しい名前をまとうだけ。
ルクスが水面で小さく光りました。まるで、その言葉に頷くように。
