はじめに
それぞれNode.jsとGoでは以下のように並行処理方式が異なるので、無限ループに遭遇すると、理屈で考えると以下のようになるはずですが……
- Node.js: mainスレッドが一つのみの実質的なシングルスレッドなので、サーバ全体が停止します
- 回避するためには
clusterなどで複数プロセスで処理します
- 回避するためには
- Go: goroutineで継続を複数スレッドに柔軟に当てられるので、サーバが簡単には停止しません
- 更にプリエンプティブ1なので、処理の最中だろうが処理を中断することができます
本当にそうなのか確かめたことがないので、他ランタイムも含めて実験してみました。
検証方法
各言語で以下の仕様の極力簡素なHTTPサーバを用意します。
-
GET /:OKを返す(正常エンドポイント) -
GET /loop: 無限ループに入る(問題エンドポイント)
手順は単純で、/loop を叩いた後に / が応答を返すかどうかで判定します。
$ curl http://localhost:PORT/ # まず正常に動くことを確認
OK
$ curl http://localhost:PORT/loop & # 無限ループを発火
$ curl -m 3 http://localhost:PORT/ # 再度リクエスト(3秒でタイムアウト)
検証環境: macOS 15.7.4 / Apple M2 Max (12コア) / arm64
各言語ごとの無限ループ時の反応
Node.js
// Node.js v24.13.1
import { createServer } from "node:http";
const server = createServer((req, res) => {
if (req.url === "/loop") {
while (true) {}
}
res.writeHead(200);
res.end("OK");
});
server.listen(3001, () => console.log("Node.js listening on :3001"));
$ curl http://localhost:3001/
OK
$ curl http://localhost:3001/loop &
$ curl -m 3 http://localhost:3001/
curl: (28) Operation timed out after 3001 milliseconds with 0 bytes received
無限ループでサーバ全体が停止しました。
/loop にアクセスした瞬間にイベントループがブロックされ、他のリクエストも一切処理できなくなりました。
(worker_threadsやclusterで回避方法はあります)
Go
// Go 1.25.7
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "OK")
})
http.HandleFunc("/loop", func(w http.ResponseWriter, r *http.Request) {
for {
}
})
fmt.Println("Go listening on :3002")
http.ListenAndServe(":3002", nil)
}
$ curl http://localhost:3002/
OK
$ curl http://localhost:3002/loop &
$ curl -m 3 http://localhost:3002/
OK
無限ループでサーバが停止しませんでした。
無限ループは一つの goroutine 内で起きているだけで、他のリクエストは別の OS スレッド上の goroutine で処理されます。
では、これを12本の無限ループに増やすと……
$ for i in $(seq 1 12); do curl http://localhost:3002/loop & done
$ curl -m 5 http://localhost:3002/
OK
一定量の無限ループ下でも問題なく応答しました。
Go 1.14以降はgoroutineが非協調的にプリエンプションされるため、無限ループ中でもスケジューラがシグナルを送ってスレッドを奪い返せます。
Python (asyncio)
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "aiohttp",
# ]
# ///
from aiohttp import web
async def handle_ok(request):
return web.Response(text="OK")
async def handle_loop(request):
while True:
pass
app = web.Application()
app.router.add_get("/", handle_ok)
app.router.add_get("/loop", handle_loop)
if __name__ == "__main__":
web.run_app(app, port=3003)
$ uv run python/server.py &
$ curl http://localhost:3003/
OK
$ curl http://localhost:3003/loop &
$ curl -m 3 http://localhost:3003/
curl: (28) Operation timed out after 3001 milliseconds with 0 bytes received
Node.jsと同様に無限ループでサーバ全体が停止しました。
Node.js同様にシングルスレッドのイベントループという並行処理モデル自体の制約です。
(run_in_executor()等で回避方法はいくつかあります)
最近のJava (Virtual Thread)
// openjdk version "26" 2026-03-17
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
void main() throws IOException {
var server = HttpServer.create(new InetSocketAddress(3004), 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/", exchange -> {
var response = "OK".getBytes();
exchange.sendResponseHeaders(200, response.length);
try (var os = exchange.getResponseBody()) {
os.write(response);
}
});
server.createContext("/loop", exchange -> {
while (true) {}
});
System.out.println("Java (Virtual Thread) listening on :3004");
server.start();
}
$ java java/Server.java &
$ curl http://localhost:3004/
OK
$ curl http://localhost:3004/loop &
$ curl -m 3 http://localhost:3004/
OK
1つの無限ループでは単純には停止しませんでした。
しかし、以下のように12個無限ループを起動すると……
$ for i in $(seq 1 12); do curl http://localhost:3004/loop & done
$ curl -m 5 http://localhost:3004/
curl: (28) Operation timed out after 5001 milliseconds with 0 bytes received
一定量の無限ループ下では応答しなくなりました。
まとめ
| 言語/ランタイム | 並行処理モデル | 無限ループ時 |
|---|---|---|
| Node.js | イベントループ (シングルスレッド) | 全停止 |
| Python (asyncio) | イベントループ (シングルスレッド) | 全停止 |
| Go | goroutine (プリエンプティブ) | 停止しない |
| Java (Virtual Thread) | Virtual Thread (協調的) | コア数分で停止 |
このシナリオにおいてはGoが最強ですね。
Rustは実験していませんが、tokioなどのasyncランタイムもワーカースレッドプールが固定サイズのため、Javaと似たような結果になると思われます。
方式ごとの特性は把握しておきたいものです。
-
監視スレッドによるシグナル送信&シグナルハンドラによる強制切替方式 ↩