Node.JS中的事件循环是多么可预测

时间:2014-06-15 01:38:57

标签: javascript multithreading node.js

如果您观察到像this那样简单的客户端JS,那么直观的是,如果我们遇到setTimeouts,时间长度(在本例中为0毫秒)确定是什么JS运行时实际将其添加到队列的时间间隔,如here所述。

然而,在Node.JS中,如果我有一些基本上是异步的api调用(比如fs.readFile()),并且在相同的代码库中,如果我有一个简单的setTimeout,我的理解是事件循环将开始读取该文件,如果已被读取,它继续并将其排队,以便在主节点线程不进行任何顺序操作时可以触发相应的回调。我的问题是,这个概念是"添加setTimeout回调"仅在特定超时后仍然保持(与类客户端JS相反)。具体来说,这是一个例子:

const fs = require('fs');
// Set timeout for 2 secs
setTimeout(function() { 
  console.log('Timeout ran at ' + new Date().toTimeString()); 
}, 2000);
// Read some file
fs.readFile('sampleFile.txt', function(err, data) {
   if(err) {
     throw err;
   }
   console.log(data.toString() + " " + new Date().toTimeString();
}
var start = new Date();
console.log('Enter loop at: '+start.toTimeString());
// run a loop for 4 seconds
var i = 0;
// increment i while (current time < start time + 4000 ms)
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}
console.log('Exit loop at: ' +new Date().toTimeString() +'. Ran '+i+' iterations.');

我得到的输出是:

在GMT-0700(PDT)18:22:14进入循环 退出循环:格林威治标准时间-0700(太平洋标准时间)18:22:18。冉33980131迭代。 超时时间为格林威治标准时间-0700(太平洋时间)下午18:22:18 sampleFileContents 18:22:18 GMT-0700(PDT)

这是否意味着在完全读取文件之前,setTimeout回调+消息以某种方式放置在事件循环队列中?这告诉我三件事中的一件:setTimeout回调+消息被放置在准备好在2秒后触发的队列和下一个可用的滴答。或者实际读取sampleFile.txt的时间超过2秒。或者快速读取了sampleFile.txt,但不知何故它没有放在事件循环队列中的setTimeout之前。

我是否使用正确的心理模型来思考这个问题?我试图更深入地了解节点的内部结构,但无需深入了解libuv / libeio C代码。我已经尝试过使用超时,有趣的是当我将超时设置为大于4000毫秒时,似乎在我的输出中,我总是在实际打印超时运行的时间之前打印出sampleFileContents。 / p>

1 个答案:

答案 0 :(得分:3)

有一个问题。以下代码行是同步和阻塞的。

// increment i while (current time < start time + 4000 ms)
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}

这意味着事件循环被它劫持,并且永远不会像你期望的那样发挥作用。

fileread之前的settmeout打印可能意味着在循环开始之前的时间点已经设置了计时器,但是尚未添加fileread事件。我添加了一些代码来验证这个想法。

var readStream = fs.createReadStream('sampleFile.txt');

  readStream.on('open', function () {
    console.log('Read started ' + new Date().toTimeString());
  });

  readStream.on('data', function(data) {
  });

  readStream.on('end', function(err) {
   console.log('Read end ' + new Date().toTimeString());
  });   

setTimeout(function() {
  console.log('Timeout ran at ' + new Date().toTimeString());
}, 2000);

var start = new Date();
console.log('Enter loop at: '+start.toTimeString());

var i = 0;
while(new Date().getTime() < start.getTime() + 4000) {
  i++;
}

console.log('Exit loop at: ' +new Date().toTimeString() +'. Ran '+i+' times.');

输出结果为:

Enter loop at: 22:54:01 GMT+0530 (IST)
Exit loop at: 22:54:05 GMT+0530 (IST). Ran 34893551 times.
Timeout ran at 22:54:05 GMT+0530 (IST)
Read started 22:54:05 GMT+0530 (IST)
Read end 22:54:05 GMT+0530 (IST)

这证明了我的理论,他们从未同时进行过。至于为什么会发生这种情况我相信fs事件需要至少有一个滴答排队并正确发送。但是超时会立即增加。由于您在添加fileread事件之前锁定了事件循环,因此在循环结束后,它在超时处理程序之后排队。

您可以尝试在没有循环的情况下运行代码,输出将是

Enter loop at: 22:57:15 GMT+0530 (IST)
Exit loop at: 22:57:15 GMT+0530 (IST). Ran 0 iterations.
 22:57:15 GMT+0530 (IST)
Timeout ran at 22:57:17 GMT+0530 (IST)

如果读取完成。