You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js的长期同步循环中捕获信号?

Node.js同步循环中处理信号与I/O事件的折中方案

问题背景

我写了一段包含同步死循环的代码,发现进入循环后Node.js完全无法处理SIGINT信号或其他I/O事件:

const signals = { INT: false };

process.on("SIGINT", s => {
    console.log('trapped sigint:', s);
    signals.INT = true;
});

let i = 0;
while(true){
    if(signals.INT){
        console.log('got the SIGINT'); break;
    }
    console.log('process.pid:',process.pid, 'next val:', i++);
}

console.log('exited loop, done');
process.exit(0);

原因很清楚:Node.js的事件循环被同步代码完全阻塞,所有异步任务(包括信号回调)都得不到执行机会。

换成异步循环虽然能解决问题,但循环执行速度会大幅下降:

const signals = { INT: false };

process.on("SIGINT", s => {
    console.log('trapped sigint:', s);
    signals.INT = true;
});

let i = 0;
(async () => {
    while(true){
      if(signals.INT){
        console.log('got the SIGINT'); break;
      }
      await Promise.resolve(); // 必须await微任务才能让出事件循环
      console.log('process.pid:',process.pid, 'next val:', i++);
    }
    console.log('exited loop, done');
    process.exit(0);
})()

我需要一个折中方案:既保留接近同步循环的速度,又能让Node.js及时处理信号和I/O事件。

可行的折中方案

核心思路是批量执行同步迭代,定期主动让出事件循环,让Node.js有机会处理待执行的异步任务。

1. 用process.nextTick实现高速度循环(优先处理微任务)

这种方式每次执行固定数量的同步迭代后,通过process.nextTick调度下一轮循环,开销极小,速度几乎和纯同步循环一致:

const signals = { INT: false };

process.on("SIGINT", s => {
    console.log('trapped sigint:', s);
    signals.INT = true;
});

let i = 0;
const batchSize = 1000; // 可根据需求调整,越大速度越快,响应稍慢

function runBatch() {
    let count = 0;
    // 批量执行同步逻辑
    while (count < batchSize && !signals.INT) {
        console.log('process.pid:', process.pid, 'next val:', i++);
        count++;
    }

    // 检查退出条件
    if (signals.INT) {
        console.log('got the SIGINT');
        console.log('exited loop, done');
        process.exit(0);
    }

    // 让出事件循环,处理微任务后继续
    process.nextTick(runBatch);
}

runBatch();

2. 用setImmediate兼顾I/O事件处理

如果你的场景需要同时处理文件、网络等I/O事件,用setImmediate更合适——它会在当前事件循环的I/O阶段结束后调度下一轮循环,确保I/O任务被处理:

const signals = { INT: false };

process.on("SIGINT", s => {
    console.log('trapped sigint:', s);
    signals.INT = true;
});

let i = 0;
const batchSize = 1000;

function runBatch() {
    let count = 0;
    while (count < batchSize && !signals.INT) {
        console.log('process.pid:', process.pid, 'next val:', i++);
        count++;
    }

    if (signals.INT) {
        console.log('got the SIGINT');
        console.log('exited loop, done');
        process.exit(0);
    }

    // 让出事件循环,优先处理I/O事件后继续
    setImmediate(runBatch);
}

runBatch();

关键注意事项

  • batchSize的调整:根据业务平衡速度和响应性。比如CPU密集型任务可以把batchSize设大(如10000),需要快速响应信号的场景可以设小(如100)。
  • 避免无效await:之前的异步循环中await console.log没用,因为console.log是同步函数,必须await一个返回Promise的操作(比如Promise.resolve())才能让出事件循环。
  • 信号响应的延迟:无论哪种方案,信号回调都要等当前批次执行完才会触发,但只要batchSize不是极大,延迟几乎可以忽略。

内容的提问来源于stack exchange,提问作者Alexander Mills

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 06:31:11