コンテンツにスキップ

【比較】スレッド・コルーチン・グリーンスレッドの違い

概要

「(OS)スレッド」「コルーチン」「グリーンスレッド」は、いずれも“複数の処理を並行に進める実行単位”ですが、誰がスケジュールするかといつ切り替わるかが決定的に違います。OSスレッドはカーネルがスケジュールし、任意のタイミングで横取り(プリエンプティブ)されます。コルーチンは自分から await/yield 等で協調的に中断・再開する仕組みで、スレッドを横取りしません。グリーンスレッド(軽量スレッド/仮想スレッド/goroutine 等)は、言語ランタイムがユーザー空間でスケジュールし、多数(M)の論理スレッドを少数(N)のOSスレッドへ多重化(M:N)します。

この3つは「軽量な並行」という共通の文脈で語られるため混同されがちですが、特にコルーチンとグリーンスレッドはしばしば同一視されます。本記事では OpenJDK(JEP 444)、Oracle 公式ドキュメント、Go 公式 “Effective Go”、Python asyncio、Kotlin 公式ドキュメントを一次情報として、スケジューリングの主体と切り替え方式で正確に分けます。

例えるなら:会議室の使い方

OSスレッドは「タイマー(OS)が一定時間で強制的に話者を交代させる司会者付き会議」。コルーチンは「話者が“ここで一旦お返しします(await)”と自発的に譲るまで交代しない会議」で、譲らない人がいると進みません。グリーンスレッドは「受付(言語ランタイム)が大勢の参加者を、少数の実際の会議室に上手に割り当てて回す」運用です。

詳細比較

項目 OSスレッド (Thread) コルーチン (Coroutine) グリーンスレッド/仮想スレッド (Green/Virtual Thread)
スケジューラ OSカーネル プログラム自身(イベントループ等) 言語/VMランタイム(ユーザー空間)
切り替え方式 プリエンプティブ(任意時点で横取り) 協調的(await/yield 等の中断点でのみ) ランタイム制御(協調的中断点で切替えることが多い)
OSスレッドとの対応 1:1(カーネルスレッドに対応) スレッドに非束縛(1スレッド上で多数を回せる) M:N(多数の論理スレッドを少数のOSスレッドに多重化)
代表例 POSIX threads, Java platform thread Python async/await, Kotlin coroutine, C# async Go goroutine, Java virtual thread (JEP 444), Erlang プロセス
生成コスト/規模 重い(数千規模で資源逼迫しやすい) 非常に軽い(数百万規模も可) 軽い(Java 仮想スレッドや goroutine は大量生成可)
スタック OS割当の比較的大きな固定スタック スタックレス/スタックフルは実装による 小さく可変(例: goroutine は小さく始まり伸長)
ブロッキング呼び出しの影響 OSが他スレッドへ切替(並行は保たれる) 非同期非対応のブロッキング呼び出しはイベントループ全体を止める ランタイム対応のブロッキングは“unmount”され担体OSスレッドは解放(未対応操作は担体ごとブロックし得る)
速度 vs スケール 基準 主にI/O並行の効率化 「速くはならない。スケール(スループット)のための仕組み」(Oracle)

補足:

  • OSスレッド:Oracle のドキュメントは仮想スレッドとの対比で、従来のプラットフォームスレッドがOSスレッドに 1:1 で対応する重い単位であることを前提に説明しています。横取り(プリエンプション)はカーネルが行います。
  • コルーチン:Python asyncio は「コルーチンは async/await 構文で宣言し、イベントループは協調スケジューリングで一度に1タスクを実行する」と述べます。Kotlin 公式も「コルーチンはスレッドをブロックする代わりに中断できる」「特定スレッドに束縛されず、別スレッドで再開でき、スレッドより遥かに軽い」と明言します。Kotlin 公式の例では 50,000 個でコルーチン約 500MB に対し JVM スレッドは最大約 100GB とされます。
  • グリーンスレッド/仮想スレッド:JEP 444 は仮想スレッドを「OSではなく JDK が提供する軽量スレッド」とし、M:N スケジューリング(多数の仮想スレッドを少数のOSスレッドで実行)、スケジューラは work-stealing の ForkJoinPool、ブロッキング操作の大半で仮想スレッドが unmount され担体(carrier)と背後のOSスレッドが解放される、と定めます。Oracle は「仮想スレッドはより速いスレッドではない。コードが速く走るわけではなく、スケール(高スループット)のために存在する」と明記。Go の “Effective Go” は goroutine を「同一アドレス空間で他の goroutine と並行実行される関数。軽量でスタック確保程度のコスト。複数のOSスレッドに多重化され、1つがI/O待ちでブロックしても他は走り続ける」と定義します。

よくある誤解

  • 「コルーチンとグリーンスレッドは同じもの」は誤り。 似て見えますが層が違います。コルーチンは“中断・再開できる計算”という言語機能で、典型的には1スレッド上でイベントループが協調スケジュールします(Python asyncio)。グリーンスレッド/仮想スレッドはランタイムが多数の論理スレッドを少数のOSスレッドに M:N で割り当てる“スレッド風の実行単位”です(JEP 444 の仮想スレッド、Go の goroutine)。両者を実装基盤に使う言語もありますが、概念としては別レイヤーであり同一視は不正確です。

  • 「コルーチン/グリーンスレッドなら何でも勝手に並行化されるので、重い処理を普通に呼んでよい」は誤り。 協調的モデルでは中断点でしか切り替わりません。async 対応でないブロッキング処理(同期I/O・重いCPU処理)をコルーチン内で素直に呼ぶと、イベントループ全体が止まります(asyncio は別スレッド実行の asyncio.to_thread() を用意)。仮想スレッドでも、JDK が unmount 対応していない一部の操作(一部のファイル操作や Object.wait() など)は担体OSスレッドごとブロックし得ます(これらのピン留め挙動は Java 21 / JEP 444 時点の仕様であり、例えば synchronized によるピン留めは後の JDK(JEP 491, Java 24)で解消されています)。

  • 「グリーンスレッド/仮想スレッドはOSスレッドより“速い”」は誤り。 Oracle は「仮想スレッドはより速いスレッドではない。コードが速く走るわけではなく、スケール(高スループット)のために存在し、低遅延(速度)のためではない」と明言します。利点は1コードあたりの速度ではなく、膨大な数の並行タスクを低資源で捌けるスケーラビリティです。

  • 「コルーチンはOSスレッド上で動く軽量なOSスレッドだ」は誤り。 コルーチンはOSがスケジュールする実行単位ではありません。Kotlin 公式の通り「特定スレッドに束縛されず、ある時点でスレッドAで中断し別スレッドで再開し得る」協調的な計算であり、横取りもOSが行いません。OSスレッド/カーネルスケジューリングと混同しないことが重要です。

実務での選び分け

  • OSによる公平なプリエンプションが要る/既存のブロッキングAPIをそのまま使う薄い並行 → OSスレッド(プラットフォームスレッド)。ただし数千規模で資源を圧迫しやすい点に注意。
  • 大量のI/O待ちを1〜少数スレッドで効率良く捌きたい(Webクライアント、ソケット多重化) → コルーチン(async/await)。ただしブロッキング処理はオフロード(例: asyncio.to_thread)。
  • 「1リクエスト=1スレッド」の素直な同期スタイルのまま莫大な同時接続を捌きたい → グリーンスレッド/仮想スレッド(Java 仮想スレッド, Go goroutine)。コードは同期的に書け、ランタイムが M:N で多重化。
  • CPUバウンドな重い計算を並列化したい → どれも“それ単体”では解にならない。OSスレッド/プロセスで実コアに分散する(協調モデルやGIL等の別制約も考慮)。

ひとことまとめ

OSスレッドは「カーネルがプリエンプティブにスケジュールする 1:1 の重い実行単位」、コルーチンは「中断点で協調的に譲るスレッド非束縛の言語機能」、グリーンスレッド/仮想スレッドは「ランタイムが M:N で多重化する軽量スレッド」であり、特にコルーチンとグリーンスレッドは別レイヤーなので同一視しないことが要点です。

出典・参考