Node.js worker_threadsでCPU集約タスクをメインスレッドから分離する
問題
Node.jsで画像リサイズや大量のJSONパースなどのCPU集約的な処理を実行すると、イベントループがブロックされます。APIレスポンスが停止し、他のリクエストも処理できなくなります。
解決方法
worker_threadsモジュールを使って、重い処理を別スレッドで実行します。
// worker.js - ワーカースレッドで実行されるコード
const { parentPort, workerData } = require('worker_threads');
function heavyCalculation(data) {
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.sqrt(i) * Math.random();
}
return result;
}
const result = heavyCalculation(workerData);
parentPort.postMessage(result);
// main.js - メインスレッド
const { Worker } = require('worker_threads');
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData: data });
worker.on('message', resolve);
worker.on('error', reject);
});
}
app.get('/heavy', async (req, res) => {
const result = await runWorker({ iterations: 10_000_000 });
res.json({ result });
// この間、他のリクエストは正常に処理されます
});
繰り返し使う場合は、ワーカープールでオーバーヘッドを削減できます。
const os = require('os');
const poolSize = os.cpus().length;
const workers = Array.from({ length: poolSize },
() => new Worker('./worker.js')
);
ポイント
worker_threadsはNode.js組み込みモジュールなので、追加インストール不要ですworkerDataでデータを渡し、parentPort.postMessage()で結果を返します- メインスレッドのイベントループをブロックしないため、APIの応答性が維持されます
- 繰り返し使用する場合はワーカープールパターンで生成オーバーヘッドを削減しましょう