同時実行モデルの比較: OS スレッド、イベント ループ、ゴルーチン、アクター、非同期/待機
同時実行モデルの決定版マップ: OS スレッド (Java)、グリーン スレッド/ゴルーチン (Go)、 シングルスレッド イベント ループ (Node.js)、アクター モデル (Erlang/Elixir)、async/await (Python/Rust)。 いつどれを使用するか、長所/短所、および比較ベンチマーク。
競争が難しいから
同時実行性とは、「進行中の」複数のタスクを管理するプログラムの能力です。 同時に - 必ずしも並行している必要はありません。の 並列処理 それは処刑だ 複数の物理コアで同時に実行できます。 2 つの概念間の混乱が多くの原因となっています。 バグや間違ったアーキテクチャ上の決定。
2026 年には 5 つの主要な競争モデルがあり、それぞれに異なるトレードオフがあります。そうではない 絶対的な「最適な」モデルがあります。選択はワークロードのタイプ (I/O バウンドか、 CPU 依存)、言語、エコシステム、レイテンシの要件。
モデル 1: OS スレッド (Java、C++)
最も伝統的なモデル: 各同時実行ユニットは スレッド オペレーティングシステムの。カーネルは、スケジューリング、コンテキストの切り替え、および通信を処理します。 ミューテックスで保護された共有メモリを介してスレッド間で通信します。
// Java: thread tradizionale vs virtual thread (Java 21)
// Thread OS tradizionale — costoso: ~1MB stack, scheduling kernel
Thread platformThread = new Thread(() -> {
processRequest(); // blocca il thread OS durante I/O
});
platformThread.start();
// Virtual Thread (Java 21, Project Loom) — leggero: ~2KB stack iniziale
Thread virtualThread = Thread.ofVirtual().start(() -> {
processRequest(); // blocca solo il virtual thread, non il carrier
});
// Un milione di virtual thread sono praticabili
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> handleRequest());
}
} // attende il completamento
長所: シンプルなメンタルモデル、複数のコアを自動的に活用、成熟したライブラリ
に対して: 高価な OS スレッド (1MB 以上のスタック、スイッチング オーバーヘッド)、共有メモリの競合状態、スケーリングが数千スレッドに制限されている
いつ: CPU バウンドの作業、スレッド プールを使用した Java/C++、混合負荷
パターン 2: シングルスレッド イベント ループ (Node.js、JavaScript)
JavaScript はシングルスレッドです。実行スレッドは 1 つだけで、イベント ループは 1 つだけです。
コールバックを管理します。非同期 I/O (ネットワーク、ファイルシステム) はオペレーティング システムに委任されます。
経由 libuv 完了した操作はコールバック キューに入れられます。
// Node.js: event loop in azione
// Tutto esegue sullo stesso thread — nessun race condition!
const http = require('http');
http.createServer((req, res) => {
// Questa callback non blocca il thread
fetchUserData(req.userId)
.then(user => {
return fetchOrders(user.id); // altra I/O non bloccante
})
.then(orders => {
res.json({ user, orders });
})
.catch(err => res.status(500).json({ error: err.message }));
}).listen(3000);
// Async/await (zucchero sintattico sopra Promise):
async function handleRequest(req, res) {
const user = await fetchUserData(req.userId); // non blocca il thread
const orders = await fetchOrders(user.id); // non blocca il thread
res.json({ user, orders });
}
長所: 競合状態なし (シングルスレッド)、I/O バウンドの非常に高い同時実行性、巨大な npm エコシステム
に対して: CPU バウンドによるすべてのブロック、コールバック地獄 (async/await によって軽減)、真の並列処理のためのワーカー スレッド
いつ: 多数の同時 I/O リクエストを備えた API サーバー、リアルタイム アプリ、BFF レイヤー
モデル 3: ゴルーチンとチャネル (Go)
Go の実装 順次プロセスの通信 (CSP): 超軽量のゴルーチン (2KB の初期スタック、動的に増加) 型付きチャネル経由で通信されます。囲碁のマントラ: 「記憶を共有することでコミュニケーションするのではなく、コミュニケーションすることで記憶を共有する。」
// Go: goroutine e channel
package main
import (
"fmt"
"sync"
)
// Fan-out/Fan-in pattern con goroutine
func processItems(items []Item) []Result {
results := make(chan Result, len(items))
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(i Item) { // avvia goroutine — ~2KB stack
defer wg.Done()
result := processItem(i) // eseguito concorrentemente
results <- result
}(item)
}
// Chiudi il channel quando tutte le goroutine completano
go func() {
wg.Wait()
close(results)
}()
// Raccoglie i risultati
var collected []Result
for r := range results {
collected = append(collected, r)
}
return collected
}
// Channel per comunicazione sicura tra goroutine
func producer(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i // invia sul channel (blocca se pieno)
}
close(ch)
}
func consumer(ch <-chan int) {
for v := range ch { // riceve finché il channel è aperto
fmt.Println(v)
}
}
長所: 超軽量ゴルーチン (数百万個が実行可能)、チャネルが競合状態を防止、ランタイムがスケジューリングを管理
に対して: CSP モデルには学習が必要、チャネルが閉じられていない場合は goroutine リーク、Go 1.18 より前ではジェネリックスなし
いつ: 高い I/O 同時実行性を備えたバックエンド サービス、データ パイプライン、CLI ツール
パターン 4: 非同期/待機 (Python、Rust)
非同期/待機は 協力競争: タスクは明示的に
I/O待機ポイントでの制御(await)。 JavaScriptのイベントループとは異なります。
(組み込みランタイム)、Python と Rust には明示的なランタイムが必要です (asyncio、東京)。
// Python: asyncio con TaskGroup (Python 3.11+)
import asyncio
import aiohttp
async def fetch_url(session: aiohttp.ClientSession, url: str) -> str:
async with session.get(url) as response:
return await response.text()
async def fetch_all_parallel(urls: list[str]) -> list[str]:
async with aiohttp.ClientSession() as session:
# TaskGroup garantisce che tutti i task completino o vengano cancellati
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch_url(session, url)) for url in urls]
return [task.result() for task in tasks]
# Rust: Tokio async/await (zero-cost)
use tokio::time::{sleep, Duration};
async fn fetch_data(id: u64) -> String {
sleep(Duration::from_millis(100)).await; // simula I/O
format!("data_{}", id)
}
#[tokio::main]
async fn main() {
// Join concorrente senza allocazioni aggiuntive (zero-cost)
let (r1, r2, r3) = tokio::join!(
fetch_data(1),
fetch_data(2),
fetch_data(3),
);
println!("{}, {}, {}", r1, r2, r3);
}
長所: 待機ポイントの明示的な制御、共有スタックでの競合状態なし、Rust でのコストゼロ
に対して: 「非同期病」 (非同期を呼び出す場合、すべての関数は非同期でなければなりません)、より複雑なデバッグ
いつ: I/O バウンド (Web スクレイピング、API 呼び出し) には Python、高パフォーマンス システムには Rust
モデル 5: アクター モデル (Erlang/Elixir、Akka)
アクター モデルは最も孤立しています。各アクターは独自のプライベート状態を持ち、通信します。 メッセージ経由のみ。共有メモリやミューテックスは存在せず、すべてのアクターが 独立した軽量プロセス。
// Elixir: GenServer (actor model)
defmodule Counter do
use GenServer
# Interfaccia pubblica
def start_link(initial \\ 0) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def increment() do
GenServer.call(__MODULE__, :increment)
end
def get_count() do
GenServer.call(__MODULE__, :get)
end
# Implementazione (private)
def init(initial), do: {:ok, initial}
def handle_call(:increment, _from, count) do
{:reply, count + 1, count + 1} # reply, valore_risposta, nuovo_stato
end
def handle_call(:get, _from, count) do
{:reply, count, count}
end
end
# La BEAM VM può avere milioni di processi leggeri
# con supervisione automatica (OTP supervisor tree)
長所: 完全な分離 (アクターのクラッシュは伝播しない)、設計による耐障害性、ネイティブ分散
に対して: メッセージのシリアル化オーバーヘッド、多くのアクターを含むシステムのデバッグは複雑
いつ: フォールトトレラントな分散システム、通信事業、リアルタイム ゲーム、数百万もの接続を伴う IoT
比較ベンチマーク
コンペティション: モデル選択ガイド
- 多数の I/O リクエスト (Web API サーバー): Goroutine または Node.js イベント ループ - どちらも数万の同時実行に拡張可能
- CPU バウンド (ML 推論、エンコード): ワーカー プールを備えた Java または Go OS スレッド - すべてのコアを使用
- 超低遅延 (取引、ゲーム): Rust Tokyo または Go — 実行時のオーバーヘッドがゼロ
- 分散型フォールトトレランス (通信、IoT): Elixir/Erlang アクター モデル — ネイティブ監視ツリー
- データサイエンス、スクリプティング: I/O 用の Python 非同期、CPU バウンド用のマルチプロセッシング
- Javaエンタープライズチーム: Java 21 仮想スレッド — クラシック スレッドと同じメンタル モデル、ゴルーチンのようなスケール
結論
普遍的に優れた競争モデルは存在しません。正しい選択は以下によって決まります ワークロード (I/O 対 CPU)、チーム別 (スキルと好み)、エコシステム別 (利用可能なライブラリ) および非機能要件 (レイテンシ、スループット、フォールト トレランス)。
シリーズの次の記事では、各モデルについて詳しく説明します。 JavaScript イベント ループは、Node.js の最も誤解されているコンポーネントです。







