简体   繁体   English

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

[英]How predictable is the event loop in Node.JS

If you observe simple client-side JS like this , it's intuitive that if we run into setTimeouts, the time length (in this example's case, 0 ms) determine what is the interval AFTER which the JS runtime actually adds this to the queue as explained here . 如果您观察到像这样的简单客户端JS,很直观,如果遇到setTimeouts,则时间长度(在本示例中为0 ms)确定JS运行时实际上将其添加到队列后的时间间隔,如下所述在这里

In Node.JS, however, if I have some api invocation that is fundamentally async (like fs.readFile()) and in the same codebase if I have a simple setTimeout, my understanding is that the event loop will start reading the file, and if has been read, it goes ahead and queues it up so that the appropriate callback can be fired once the main node thread isn't doing any sequential operations. 但是,在Node.JS中,如果我有一些基本上是异步的api调用(如fs.readFile()),并且如果我有一个简单的setTimeout,则在同一代码库中,我的理解是事件循环将开始读取文件,如果已经读取,它将继续进行排队并将其排队,以便一旦主节点线程未执行任何顺序操作就可以触发适当的回调。 My question is that does this notion of "adding the setTimeout callback" only AFTER the specific timeout still hold (in contrast to class client-side JS). 我的问题是,仅在特定超时仍然有效之后(与类客户端JS相反),“添加setTimeout回调”这一概念是否存在? Specifically, here is an example: 具体来说,这是一个示例:

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.');

The output i get for this is: 我为此得到的输出是:

Enter loop at: 18:22:14 GMT-0700 (PDT) Exit loop at: 18:22:18 GMT-0700 (PDT). 在以下时间进入循环:GMT-0700(PDT)18:22:14(退出)在以下时间退出循环:GMT-0700(PDT)18:22:18。 Ran 33980131 iterations. 跑了33980131次迭代。 Timeout ran at 18:22:18 GMT-0700 (PDT) sampleFileContents 18:22:18 GMT-0700 (PDT) 超时在格林尼治标准时间07:00(PDT)18:22:18运行SampleFileContents格林尼治标准时间07:00(PDT)18:22:18

Does this mean that somehow the setTimeout callback+message was placed on the event loop queue BEFORE the file could be read completely? 这是否意味着在完全读取文件之前,将setTimeout回调+消息以某种方式放置在事件循环队列中? This tells me 1 of 3 things: Either the setTimeout callback+message was placed on the queue ready to be fired after 2 secs and the next available tick. 这告诉我3件事情中的1件:将setTimeout回调+消息放在队列中,准备在2秒和下一个可用滴答之后触发。 OR the time to actually read the sampleFile.txt took more than 2 seconds. 或实际读取sampleFile.txt的时间超过2秒。 OR the sampleFile.txt was read quickly, but somehow it wasn't placed ahead of the setTimeout in the event loop queue. 或快速读取了sampleFile.txt,但由于某种原因,它没有放在事件循环队列中的setTimeout之前。

Am i using the right mental model for think about this? 我是否使用正确的思维模式来考虑这个问题? I'm trying to get a deeper understanding of node's internals but without having to dive through the libuv/libeio C-code. 我试图更深入地了解节点的内部结构,而不必深入研究libuv / libeio C代码。 I've tried playing around with the timeout, and interestingly enough when I set the timeout to greater than 4000 ms, it appears that in my output, I always print out the sampleFileContents before actually printing what time the timeout ran at. 我尝试过处理超时,有趣的是,当我将超时设置为大于4000毫秒时,似乎在我的输出中,我总是打印出sampleFileContents,然后才实际打印超时时间。

There is a gotcha. 有一个陷阱。 The following lines of code are synchronous and blocking. 以下代码行是同步的和阻塞的。

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

Which means that the event loop was hijacked by it and never got to function as you would expect. 这意味着事件循环被它劫持了,并且永远无法像您期望的那样起作用。

The settmeout printing before fileread could mean that at the point of time just before the loop started the timer was already set, but the fileread events were not yet added. 在文件settmeout之前进行settmeout打印可能意味着在循环开始之前的时间点已经设置了计时器,但是尚未添加文件读取事件。 I added some more code to verify this idea. 我添加了更多代码来验证这个想法。

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.');

The output is : 输出为:

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)

Which proves my theory that they never ran concurrently. 这证明了我的理论,即它们从未同时运行。 As to why this happened I believe that fs events need at least one tick to be queued and sent correctly. 至于发生这种情况的原因,我相信fs事件至少需要一个滴答声才能排队并正确发送。 But timeouts are added instantly. 但是超时会立即添加。 Since you locked the event loop before the fileread event could be added, it was queued after timeout handler after the loop ended. 由于您在添加文件读取事件之前锁定了事件循环,因此该事件在循环结束后在超时处理程序之后排队。

You can try running your code without the loop, the output will be 您可以尝试运行没有循环的代码,输出将是

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)

if read finishes first. 如果读取先完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM