javascript - Node.JS 中事件循环的可预测性如何

标签 javascript multithreading node.js

如果您观察简单的客户端 JS,如 this ,很直观的是,如果我们遇到 setTimeouts,时间长度(在本例中为 0 毫秒)决定了 JS 运行时实际将其添加到队列之后的时间间隔,如所述 here .

但是,在 Node.JS 中,如果我有一些基本上是异步的 api 调用(如 fs.readFile()),并且在相同的代码库中如果我有一个简单的 setTimeout,我的理解是事件循环将开始读取文件,如果已被读取,它将继续并将其排队,以便在主 Node 线程不执行任何顺序操作时可以触发适当的回调。我的问题是,仅在特定超时之后“添加 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.');

我得到的输出是:

进入循环时间:18:22:14 GMT-0700 (PDT) 退出循环时间:18:22:18 GMT-0700 (PDT)。运行了 33980131 次迭代。 超时时间为 18:22:18 GMT-0700 (PDT) 样本文件内容 18:22:18 GMT-0700 (PDT)

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

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

最佳答案

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

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

这意味着事件循环被它劫持了,并且从未像您期望的那样运行。

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

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)

如果读取首先完成。

关于javascript - Node.JS 中事件循环的可预测性如何,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24225838/

相关文章:

javascript - Titanium 将数据传递给 createHTTPClient

javascript - jQuery load() 方法在第二次运行后返回空白

javascript - 如何删除符合特定条件的所有 Div 的样式?

java - 跨线程通信java

java - ExecutorService、newFixedThreadPool 和 setPrioirty

javascript - 如何禁用或隐藏 intro.js 中特定步骤的按钮?

node.js - 如何将其转换为JADE模板引擎?

javascript - 使用开发工具 - 缺少 Prop 。如何找到我的申请中要查找的类别?

c# - 端口读取中的线程速度

node.js - mongodb 和 EventEmitter 之间的问题