世界を動かす技術を、日本語で。

Pythonは非同期処理を10年間持っているのに、なぜもっと人気がないのか?

概要

  • Python 2から3への移行 は当初コミュニティを分裂させたが、最終的には大きな問題とならず
  • Python 3.5でasync/awaitが導入 され、主にWeb開発で活用
  • Python 3.14でFree-Threading(PEP 779)とMultiple Interpreters(PEP 734)が公式サポート
  • asyncはI/Oバウンド処理で特に有効 だが、CPUバウンドやファイルI/Oには制限
  • GILの存在や実装の違い により、asyncや並列処理の普及が限定的

Python 3.14と並行・並列処理の進化

  • Python 3.14 では PEP 779: Free-ThreadingPEP 734: Multiple Interpreters が公式サポート
    • Free-Threading: GILを排除 し、より細かいロックで並列実行を可能に
    • Multiple Interpreters: 標準ライブラリで複数インタプリタを管理 できる機能
  • これらの新機能は Pythonでの並行・並列処理の大きな前進
  • async/awaitは10年以上前から存在 し、主にWeb開発で利用
  • しかし、 主要Webフレームワークでのasync対応は限定的
    • FastAPI: 完全なasync対応
    • Django:一部async対応、ORMは未対応
    • Flask: 同期処理が基本
    • SQLAlchemy: 2023年にasyncio対応

asyncが普及しない理由

  • asyncはI/Oバウンド処理(特にネットワーク)で真価を発揮
    • 複数のHTTPリクエストなど、 同時発行・待機が容易
  • ファイルI/Oやサブプロセス等はasyncioで非同期化困難
    • aiofilesなどのサードパーティ利用時も 内部的にはスレッドプールに依存
    • OSのファイルI/O非同期API(io_uring等)は セキュリティ問題や制限あり
  • 実際にはネットワークI/O以外でのasync活用は限定的
  • 主要なasyncio API一覧
    • Sleep: asyncio.sleep()
    • TCP/UDP Streams: asyncio.open_connection()
    • HTTP: aiohttp.ClientSession()
    • サブプロセス: asyncio.subprocess
    • キュー: asyncio.Queue

GILとasyncの限界

  • GIL(Global Interpreter Lock) があるため、 CPUバウンドな処理は並列化不可
  • C#のasync/awaitモデル とPythonの違い
    • C#: タスクプールで自動的にスレッド管理
    • Python: イベントループが単一スレッドで管理
  • async関数内でblocking処理があると、イベントループ全体がブロック
    • 例:time.sleep()を使うと 全体が止まる
  • I/O処理やスリープ以外はほぼブロックするため、asyncの恩恵が限定的
  • run_in_executor でスレッドプールと組み合わせることも可能だが、 設計が複雑化

Free-Threadingの意義と今後

  • Python 3.13で実験的にFree-Threading(GILなしビルド)が登場
    • 3.14でより安定化、2026年以降に本格導入を想定
  • コルーチンとスレッドの比較
    • コルーチン: メモリフットプリントが小さい、低いコンテキストスイッチコスト
    • スレッド: GILの影響を受けやすい
  • Free-Threading導入でasyncioの役割がどう変わるかは今後の課題
  • 今後10年で新しい並列処理モデルが普及するかは、実装と現場の需要次第

まとめ

  • async/awaitはI/Oバウンド処理で有用だが、用途が限定的
  • GILやOSレベルの制限が普及の障壁
  • Python 3.14の新機能で並列処理の選択肢が大幅に拡大
  • 今後はasync・Free-Threading・Multiple Interpretersの使い分けがポイント
  • 現場のニーズと実装の熟成が普及のカギ

Hackerたちの意見

サイドプロジェクトでasyncioサーバーを書いてた時期があったんだ。特に楽しかったのは、複雑な方法で反応するものを書くことだった。例えば、メッセージキューやDenon HEOS音楽プレーヤーへのTCP接続をリッスンするwebsocketsサーバーとかね。最終的には「画像ソーター」を作ったんだけど、ブラウザが画像を並列でダウンロードしようとするとハングアップしちゃってた。画像の提供はCPUに依存するべきじゃなかったし、sendfile()も使ってたんだけど、他のリクエストがCPUを占有しちゃって、sendfileをセットアップするのに必要な少しのCPUがブロックされてたんだ。だから、aiohttpからFlask APIに切り替えて、FlaskかGunicornで提供することにした。画像処理はMicrosoft IISやnginxを使ってPythonがやらなくてもいいようにしたんだ。ちょっと面倒だけど、Windowsで開発してるからWSL2内でGunicornを動かさなきゃいけないけど、うまくいってるし、サーバーのパフォーマンスについて考えなくて済むようになったよ。

それがイベント駆動サーバーの主な問題だよね。もしワークロードのどれかがCPU集約型だったら、同じスレッドで他のすべての処理がブロックされる可能性があるから、本来はすぐに応答すべきリクエストが実際にはランダムに時間がかかることになる。基本的に、CPU負荷の高い作業があるなら、そのサーバーには入れない方がいい。

あなたが言った問題を「楽しい」と感じる人がいるのは心強いね。Redisのpubsubから読み込むFastAPIのWebSocketを書くのは、ドキュメントなしでの手探り状態だよ。

著者は根本的な問題に近づいてるけど、はっきりとは言ってないね。実際、Pythonのasyncは遅すぎたんだ。導入された時には、すでに多くのI/Oを同時に処理する必要がある人たちは自分たちのワークアラウンド(フォークとか)を持ってたし、必要ない人たちはそれなしでやりくりする方法を見つけてた(マルチプロセッシングとか)。その間に、Goは良いグリーンスレッドの姿を見せてくれたし、Javaもそれをやった。さらに、JSはずっと良いasyncサポートを持ってた。でも、結局それが示したのは、asyncコードはグリーンスレッドコードに比べて単純にクソだってこと。ブロックできるグリーンスレッドコードの方が、asyncのダンスをしなきゃいけないコードよりもずっと良いのに。だから、良い解決策がすでにあったのに、なんでそれに関わる必要があるの?

でも、結局それが示したのは、asyncコードはグリーンスレッドコードに比べて単純にクソだってこと。これに関しては職場でめちゃくちゃ叩かれるけど、100%同意だよ。見た目は同期的だけど実際は非同期なコードは、変な失敗モードや特異性があって、職場のコードのasync部分にはバグが多いのをよく見る。もしかしたら私が古いだけかもしれないけど、そんなのは価値がないと思う。結局、継続やクロージャの上にある文法的な糖衣に過ぎないよ。

それにasyncが深く必要だと組み合わせると、まさにその通りだね。ネットワークスタック全体がasyncファーストである必要があって、人気のあるネットワーキングライブラリはそれに基づいて構築されている必要がある。多くのライブラリはすでにC拡張ベースで、新しいPythonの部分とはうまく噛み合ってないんだ。

プロダクションで結構な量のPythonを書いてメンテしてきた者として、最近はサーバーサイドのTypeScriptもかなり書いてるけど、これが私の答えだよ。最近当たり前になってることを一つ加えたいんだけど、手頃なマルチスレッドCPUがここ10年で本当に普及したよね。グリーンスレッドに基づくスタックは、asyncや非asyncでコードを汚すことなく「ただ動く」し、1つのコンピュートインスタンスをN個のvCPUを持つインスタンスに優雅にスケールさせることができるんだ。

でも、asyncコードはグリーンスレッドコードと比べて本当にクソだってことを示すだけだった。グリーンスレッドのネイティブUIパラダイムが人気になったら納得するけど、良いネイティブUIのストーリーを持ってる言語はみんなasyncサポートがあるみたいだね。

Asyncはコードを汚すし、async/awaitは古典的な協調マルチタスクの問題に陥る。 「これがあれをブロックするってどういうこと?」 高レベルの作業のためのメモリと実行モデルは、asyncを持たない方がいいと思う。Goはユーザーの視点から見て、うまくやっている典型的な例だと思う。

asyncioのAPIデザインはちょっと外してると思う。以前geventを多用してた私からすると、いろんな quirks や粗い部分があって、ちょっと不思議で、逆に生産性を下げてる気がする。

asyncは自体としてはかなり良い「グリーンスレッド」だよ。コルーチンはもっと良くなる可能性があるけど、実際には重なり合った問題を解決してるだけだね。同じ問題もあれば、違う問題もある。JavaScriptのasyncはタスクを整理する良い方法がないけど、これはグリーンスレッドの重要な特徴だよ。Sindre Sorhusはそれに近いライブラリをいくつか持ってるけど、まだ穴がある。コルーチンができることは、命令キャッシュを最適化すること。でも、goroutineがそれを完全に達成できるかはわからない。実装の詳細以外にそれを妨げるものはないけどね。

asyncは何かを考えるのが楽になる方法だと思うけど、たくさんの抜け道を残しちゃうよね。時には書くのが楽なだけなんだけど、その抜け道にはPythonでは提示されない隠れた責任がたくさんあるんだ(所有権みたいな)。本当に技術的に深く入りたい人は、結局Pythonを選ばないと思う。

グリーンスレッドはプログラミングしやすいけど、コストがかからないわけじゃないよ。普通のスレッドと同じように、グリーンスレッドにもスタックが必要だしね。スタックレスな非同期の良いシステムを考える価値はあると思う。Kotlinみたいな感じが一番良いと思う。Rustも所有権の問題があるけど、グリーンスレッドでも同じように存在するから、そこに向かって進んでるところだね。

同じ問題に対してもっと良い解決策はあるけど、Pythonにはないんだよね。もしそんなに高いスループットが必要なら、GoやJVM、Erlang/Elixirに移った方がいいよ。Pythonで本来やるべきじゃないことを無理にやろうとするよりもね。うまく動いてるのはすごいけど、インピーダンスミスマッチは明らかで、自然には感じられないよ。

私のasyncに関するネガティブな経験は、#3に当てはまると思う。2つのAPIを維持するのが難しいんだ。私のキャリアの中で最も記憶に残る「本当のソフトウェアエンジニアリング」のバグの一つはasync Pythonに関するものだった。FastAPIサーバーを維持してたんだけど、外部HTTPリクエストをするたびにソケットを閉じ忘れてファイルディスクリプタが漏れ続けてた。これがいくつかの形で現れたんだ。サーバーが利用可能なファイルディスクリプタを使い果たすと、新しいHTTPリクエストを受け付けるけど、情報を送信することを拒否するという奇妙な世界に陥った。リモートデバッグが難しくなるのも面白かった。時々、サーバーがOSのファイルディスクリプタを使い果たす前にメモリ不足になることもあって、それが楽しい赤いヘリングになって、「問題を修正した!」っていう早すぎるRAMのバンプを引き起こしたこともあった。正確な原因は結局わからなかったけど、1週間かけてデバッグして、FastAPI/aiohttp/asyncioのライブラリ/フレームワーク/システムスタックの誰かがasyncコンテキストを取得した後にソケットを閉じることを期待していたけど、それが実際には起こらなかったんだと思った。ライブラリやフレームワーク間のコンテキストスイッチが常にあったから、誰が(アプリケーションレイヤーの上で)それを閉じるべきだったのかを追えなかった。私の解決策は、ネイティブのPythonソケットクラスをモンキーパッチして、FastAPIのミドルウェアレイヤーを追加して、外向きのソケットが開かれるたびに、リクエストIDでソケットのマップに追加することだった。そして、リクエストが終わったら、そのマップのソケットを見て手動で閉じるようにした。これでうまくいって、サーバーは安定したし、唯一のフォローアップリクエストは「ファイルディスクリプタを手動で閉じたソケット」というメッセージをログから削除してほしいってことだった。これがログを散らかしてたからね。こうして、信頼性の高い高性能なHTTPサーバーにはPythonを好まないという私の意見の壁にまた一つレンガが積まれた。

2つのAPIを維持するのは難しい。 このポイントはあまり取り上げられてないよね。PythonとC#にasyncが入ってくるのを見た時、(当時一番注目してた2つのエコシステム)どれだけの労力がかかってるかが悲しくなった。もしグリーンスレッドにブロッキングコールを使ってたら、もっと生産的に使えたはずなのに。さらに悪いことに、asyncを実装すると、ほぼ同じだけど微妙に違う非同期APIができるのは避けられないみたい。違いは普段は気にならないけど、いざという時には問題になる。だから、プロジェクトは2つのAPIを維持するコストを払うだけじゃなくて、ユーザーもその微妙な違いに対処するコストをずっと払い続けることになる。 > 私は信頼性のある高性能なHTTPサーバーのためにPythonを好まない。 もうあまり使ってないけど、Twisted Matrixは(今も?)これに関しては素晴らしかった。2000年代には、Pythonで簡単にネットワークインターフェースを有効に使えたのはまるで超能力みたいだった。

同じような悩みを抱えてるのは俺だけじゃないって思うと安心するよね。うちもPythonのHTTPサーバーで似たようなことやってるけど、誰も解決できなくて、ContainerdがたまにOOMキルするし、みんなただ肩をすくめて次に進む感じ。

「サードパーティライブラリのバグ」がPythonのせいだとはちょっと思えないな。

まだ記事は読んでないけど、私も何か貢献できることがあるよ。数年前、PyConでasyncについて言及している講演を見たことがあって、興味を持って使い方を学びたいと思ったんだ。でも、全くドキュメントがなかった!構文は簡単に説明されてたけど、意味については全然だった。数年後に気づいたんだけど、その(非)ドキュメントはすでにJavaScriptの機能に慣れている人向けだったんだ。でも、私はJavaScriptに詳しくなかったし、そんな機能があることすら知らなかった。これがこの議論への私の小さな貢献、一つのデータポイントだね。もしPythonのasyncにドキュメントがあったり、JavaScriptのドキュメントへの参照があったら、もう少し人気があったかもしれない。

これが私の最初のPythonのasyncの経験だったんだけど(今はかなり使ってる)、ドキュメントはコルーチンやフューチャーを知ってる人向けに書かれてるんだよね。もしコルーチンやフューチャーが何か分からなかったら、Pythonのドキュメントは役に立たないと思う。ドキュメントは、async機能を探してない人には使い方を教えてくれないし、もしかしたらそれが意図的なのかもしれないけど、async機能の普及にはつながらないよね。

余談だけど、PythonはJavaScriptより先にasync/awaitを導入したんだ。あの時の主なインスピレーションはC#だったと思う。

以前は、Unyielding [1]とWhat Color Is Your Function [2]をこの問題を考えるための正しいマトリックスとしてずっと推してたんだけど、Notes on structured concurrency [3]が書かれてからは、最近はそれを指摘するようにしてる。要するに、同時実行性を見る方法はいくつかあるけど、重要なのは一つだけ。私のプログラムは正しいのか?私のプログラムを正しくするのにどれくらい時間がかかるのか?構造化された同時実行性は、その言語の構文でそれを明確にする。非構造化の同時実行性は、すべてのコードを頭の中に保持することを要求するんだ。 [1]: https://glyph.twistedmatrix.com/2014/02/unyielding.html [2]: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... [3]: https://vorpus.org/blog/notes-on-structured-concurrency-or-g...

構造化された並行性の重要性には賛成だね。特に著者が書いたTrioライブラリはおすすめだよ。[1] https://github.com/python-trio/trio

そんなに前じゃないけど、HNで読んだコメントで、Pythonのフリースレッディングのサポートのおかげで、Pythonのasyncはもう必要なくなるし、"色付き"関数のせいでフリースレッディングに負けるって言ってた。これがこの著者の意見とも一致してるみたいで、> Pythonでのスレッドを使った並列処理は常に限られていたから、標準ライブラリのAPIはかなり原始的だと思う。フリースレッディングが安定すれば、標準ライブラリにタスク並列処理APIを持つチャンスがあると思う。> 3.14では、サブインタープリタのエグゼキュータとフリースレッディング機能が、より多くの並列処理や同時実行のユースケースを実用的で便利にしていると思う。そういう場合、async APIは必要なくて、私がこの投稿で指摘した問題の多くが軽減される。Arminが最近、その問題についてもっと深く掘り下げた投稿をしたよね: https://lucumr.pocoo.org/2025/7/26/virtual-threads/ それがきっかけで、Pythonにおけるバーチャルスレッドの可能性についてのプレPEPディスカッションにたどり着いたんだけど、知っておく必要以上のことだったけど面白かったよ: https://discuss.python.org/t/add-virtual-threads-to-python/9...

あのスレッドでほとんどの人がGoのモデルを理解してないのが面白いね。特にこの提案の著者が。プリエンプションを許可しないと、ほとんどの非非同期関数は仮想スレッドで呼び出すのが安全じゃないから、ある種の色付けが必要になる。実行者をブロックする可能性があるからね。Cコードを呼び出す場合、スタックを入れ替えたり、ブロックを処理するためにOSスレッドを追加で生成したりする必要がある。それがCGoのやってることだよ。Pythonではプリエンプションが難しいかもしれないけど、それが明確に表現されてないのが残念だね。ただ単に明らかに望ましくないと拒否されてるだけ。結局、Pythonはすでに関数の色付けを持っていて、ライブラリもそれに強制されてる。今回の提案はあまり考えられてないし、遅すぎる気がする。

asyncは正しい問題に対する間違った解決策だった - 一般的なパフォーマンスの向上。フリースレッディングは、ますますマルチコアCPUの世界での賞だね。

C#はずっとフリースレッディングを持ってたけど、それでも非同期を別の機能として必要だと感じてた。C++も同じで、今はco_awaitがあるよね。

JSからasync/awaitの概念を学んだときは、そのエレガンスに本当に驚いたんだ。今では欠点がよく知られているけど、Pythonの実装には特に使いづらくするいくつかの点があったと思う。いつもの「色付き関数」の問題がある。Pythonにもそれがあるけど、さらにひどいことになってる。同期関数とasync関数があるけど、同期関数の中にはasync関数からしか呼び出せないものもあって、イベントループが存在することを期待している。一方で、他の同期関数はasync関数から呼び出すとスレッドをブロックしたり、CPUを大量に使ったり、イベントループが検出されると実行を拒否したりする。これで少なくとも4つの色ができる。APIも同じくらい複雑だよ。JSでは、コードでやり取りする3つのプリミティブがある: 同期関数、async関数、そしてプロミス。 (イベントループを理解する必要があるけど、コードには決して見えない)。一方、Pythonには、ジェネレーター、コルーチン、アウェイタブル、フューチャー、タスク、イベントループ、AsyncIteratorなどがあって、たぶん他にもいくつかある。日常的な状況での利点はあまりないのにね。async/awaitの最大の利点の一つは「恐れのない同時実行」だった。変数が明確に定義されたawaitポイントでしか変更されず、「原子的に」しか変更されないという保証があった。しかし、Pythonは最初の保証を実際には提供できない。なぜなら、スレッドコードがasyncコードと並行して実行される可能性があるから。2つ目の保証は、GILのおかげで全てのPythonコードに無料で付いてくる - それにはasyncは必要ないんだ。

一部の同期関数はasync関数からしか呼び出せない、イベントループが存在することを期待している その状況が可能なのはわかるけど、実際に見たことはないな。例を挙げてくれる?

2017年に初めてPythonでasyncを使おうとした時のことを覚えてるけど、コルーチンを作って共有ライブラリとしてエクスポートするためのGoの基本を学ぶ方が簡単だった。誇張してるわけじゃないよ。確かその時、Pythonのasync APIはまだ実験段階だったはず。

Pythonのasyncは結構クールだと思う - スレッドやマルチプロセスよりもずっと良い。ただ、言う通り、いくつかのイライラする粗い部分もある。毎回遭遇する具体的な問題がいくつかあるんだ。関数の色付けは、関数ラッパーを書くときにかなり冗長になることがある。実際の機能が非同期でなくても、非同期関数引数を扱うためにasyncにしなきゃいけないから、ほぼ同じコードを2回書く羽目になることも。コルーチン、フューチャー、タスクの違いも変だよ。直感的な理由もなく、1つは必要だけどもう1つが必要になることが多い。待機関数が特定の型でしか動かないこともある。でも、通常は簡単に変換できるから、そもそも区別する意味があるのか疑問だよね。タスクを作ってもawaitしない場合(サーバーのシナリオではあり得る)、ガーベジコレクションの影響で実行される保証がないのは変だ。こういう挙動はAPIで明確に定義されるべきだよ。

ジェネレーターはこれとは別の話だね。JSのfunction*と同じようなもんだし。確かにコルーチンでもあるけど、経験上、ジェネレーターを一般的な非同期関数とは分けておく方が使いやすいってことが分かってるから、C#やJSもそうしてるんだよね。

最後に非同期のPythonを試したとき、APIの複雑さに本当に戸惑ったよ。他の非同期システムとは全然違うし、僕がサーバー用のPythonを書いてた頃に流行ってたgeventやtwistedとは全く別物だね。

わあ、sync_to_asyncやasync_to_syncのトランスフォーマーを使うのがどれだけ miserable かについてあまり見てなかった。一般的に、GILのせいで開発されたアーキテクチャ、例えばCeleryやgunicornみたいなやつは、async/awaitが解決する問題のほとんどを、少しだけマシな水平スケーリングで処理してると思う。多くのasyncコードの問題は、実行している単一のマシンを超えて考えない傾向があることで、考え始めた時には、より良い水平スケーリングのために再アーキテクチャが必要になる。ほとんどのPythonアプリケーション、特にウェブ開発では、Celeryみたいなもので始めれば大丈夫だと思うよ。

sync_to_asyncやasync_to_syncも、Djangoの開発者がスレッドプールランタイムをラップするために作ったライブラリ、asgirefの一部だってことも忘れないで!

個人的には、asyncioの問題は、めちゃくちゃ複雑すぎることだと思う。僕の経験では、anyio(https://github.com/agronholm/anyio)がasyncioの上にもっと良いインターフェースを提供してるよ。asyncioをバックエンドとして使えるから、asyncioエコシステムとの互換性も保たれてるしね。例えば、FastAPIはanyioを使ってる。ここでグリーンスレッドについて話してるスレッドであまり言われてないことの一つはキャンセルについてなんだ。anyioの大きな利点は、キャンセルを本当に簡単に扱えることだと思う。asyncioだとキャンセルは結構難しいし、グリーンスレッドだとキャンセルはほぼ不可能だよ。

[遅延]