Nodejs有关回调被阻塞的疑问

Nodejs有关回调被阻塞的疑问

Node是非阻塞IO大家都知道,不过我有个疑问:


var http = require('http');

http.createServer(function(req, res){
 dosomething(function(data){
  dothing....................    //这里在做一些事
  res.writeHead(200);
  res.end("<h1>Hi!</h1>");
 });
}).listen(8001);

这里创建一个http服务器,如果同时有多个请求进来,但是如果回调函数里面dothing....这里要消耗很多时间,那么就算其他的请求进来不也是会被阻塞吗?(也就是说如果回调函数需要消耗很多的时间,被回调函数给挡住了,其他的请求不也是会被阻塞吗?)毕竟Node是单线程的!

请问大家有没有遇到这个问题?都是怎么解决的啊??


4 回复

标题:Nodejs有关回调被阻塞的疑问

内容: Node.js 是一个非阻塞 I/O 的运行环境,但有时候开发者可能会对某些情况产生疑惑。例如,在处理一些耗时的操作时,即使 Node.js 是单线程的,我们希望了解它如何处理这种场景。

假设你有一个 HTTP 服务器,当接收到请求时会执行一些耗时的操作。在这种情况下,其他请求是否会被阻塞呢?

让我们来看一个例子:

var http = require('http');

http.createServer(function(req, res) {
  dosomething(function(data) {
    // 这里在做一些耗时的事
    for (let i = 0; i < 1e9; i++) {} // 模拟耗时操作
    res.writeHead(200);
    res.end("<h1>Hi!</h1>");
  });
}).listen(8001);

在这个例子中,dosomething 函数内部模拟了一个耗时的操作。虽然 Node.js 是单线程的,但是 JavaScript 的事件循环机制可以确保在执行耗时操作时不会阻塞其他请求。

解释

尽管 Node.js 是单线程的,但它的事件循环机制使得它可以处理并发请求。当一个请求进入并且触发 dosomething 函数时,Node.js 会将该任务放入事件队列中,并继续处理其他请求。只有在当前任务完成后,才会处理事件队列中的下一个任务。

如何解决

如果 dosomething 中的操作非常耗时,可以考虑使用异步方法或第三方库来优化性能。例如,可以使用 setTimeoutsetImmediate 来让 Node.js 在处理其他任务后再返回执行该操作。

function dosomething(callback) {
  setTimeout(() => {
    callback("data");
  }, 1000); // 模拟耗时操作
}

http.createServer(function(req, res) {
  dosomething(function(data) {
    // 这里在做一些耗时的事
    res.writeHead(200);
    res.end("<h1>Hi!</h1>");
  });
}).listen(8001);

通过这种方式,dosomething 函数会在后台异步执行,不会阻塞主线程。这样,其他请求就可以继续被处理,而不会受到阻塞。

总结来说,Node.js 能够很好地处理并发请求,即使在处理耗时操作时也不会阻塞其他请求。通过合理利用异步方法和事件循环机制,可以进一步提升应用的性能和响应能力。


是的。如果dothing是同步的就没法解决。 所以node.js不适合CPU密集型

我的经验是取消cluster模式,node.js只用单进程,只做最基本的服务。耗CPU的事情,用child_process.exec交给别的程序去做。举个例子,如果你需要对图片做操作,就调用imagemagick。

你的问题非常普遍。确实,尽管 Node.js 是非阻塞 IO 模型,但如果某个回调函数执行了大量计算或长时间运行的任务,仍然可能导致事件循环被阻塞,进而影响其他请求的处理。

示例代码

假设我们有一个耗时操作,比如生成一个大数组的平方根:

var http = require('http');

http.createServer(function(req, res) {
  doSomething(function(data) {
    // 假设这是一个需要大量计算的操作
    var result = calculateSquareRoots(data);
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Result: ${result}\n`);
  });
}).listen(8001);

function doSomething(callback) {
  callback('large dataset');
}

function calculateSquareRoots(data) {
  let result = 0;
  for (let i = 0; i < data.length; i++) {
    result += Math.sqrt(i);
  }
  return result;
}

在这个例子中,calculateSquareRoots 函数会花费大量时间进行计算,这会导致其他请求被阻塞。

解决方案

为了防止这种情况发生,可以使用以下几种方法:

  1. 使用 setImmediateprocess.nextTick:将长时间运行的函数移到事件队列的末尾,确保当前任务完成后再执行。

    function calculateSquareRoots(data) {
      setImmediate(() => {
        let result = 0;
        for (let i = 0; i < data.length; i++) {
          result += Math.sqrt(i);
        }
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end(`Result: ${result}\n`);
      });
    }
    
  2. 使用 Worker 线程:将耗时任务放到子进程中,避免阻塞主线程。

    const { Worker, isMainThread, parentPort } = require('worker_threads');
    
    if (isMainThread) {
      http.createServer(function(req, res) {
        const worker = new Worker(__filename);
        worker.postMessage('large dataset');
        worker.on('message', result => {
          res.writeHead(200, { 'Content-Type': 'text/plain' });
          res.end(`Result: ${result}\n`);
        });
      }).listen(8001);
    } else {
      parentPort.on('message', data => {
        let result = 0;
        for (let i = 0; i < data.length; i++) {
          result += Math.sqrt(i);
        }
        parentPort.postMessage(result);
      });
    }
    
  3. 使用异步函数和 await:如果使用 ES6 的 async/await,可以将耗时操作放在异步函数中,使代码更易读且避免阻塞事件循环。

通过这些方法,可以有效避免长时间运行的任务导致的阻塞问题。

回到顶部