如何在使用IO操作时不让libuv的任务过饱和



我使用Typescript,因此libuv执行任何IO操作。在我的特定场景中,我将获取给定文件的指纹散列。为了说明我的问题,假设输入文件是1TB的文件。为了获得文件的指纹,我可以通过文件流打开文件并更新哈希值:

return new Promise((resolve, reject) => {
const hash = crypto.createHash('sha256');
const fh = fse.createReadStream(filepath, {
highWaterMark : 100000000
});
fh.on('data', (d) => { hash.update(d); });
fh.on('end', () => {
resolve(hash);
});
fh.on('error', reject);
});

上面的例子是相当慢的考虑到它的顺序方法。所以我想到的一个更快的方法是将计算分成N个块,像这样:

let promises = [];
for (let i = 0; i < N; ++i) {
promises.push(calculateFilePart(file, from, to));
}
return Promise.all(all);

在上面的例子中,假设N是1000000,这是否意味着libuv同时在后台启动1000000个异步I/O操作?还是libuv自动将它们分批排队以避免IO请求过饱和?

任何帮助在这个主题是非常感谢!

我将尽可能简单地总结一些关键概念。我将在下面留下链接供参考,以便您核实事实。

Promise将一个任务添加到称为微任务队列的东西中。在事件循环的每次迭代中,当调用堆栈为空时,将处理来自微任务队列的任务。这被称为tick。因此,每隔一秒,微任务队列中的一些任务将被处理。

对于每个进程刻度,有一个最大深度(process.maxTickDepth)。这指定要从微任务队列中卸载并推入调用堆栈的任务数量。

算法的主要部分包括读取内容,这是一个I/O操作。这些操作被推入一个单独的队列,称为宏任务队列。当调度宏任务操作完成并且具有指定的内容块时,读取操作的事件处理程序将排队进入微任务队列,以便在下一个滴答时进行处理。

给定您的代码片段和约束,如果max depth为1000,那么为了使您的算法完全更新哈希,至少需要传递N / 1000 = 1000000 / 1000 = 1000个tick。这意味着Node.js进程每个tick只处理特定数量的任务。

我希望这能给你提供你所寻找的理解。

引用:

Node.js Under The Hood #3 - Deep Dive Into Event Loop

MDN Documentation on Promise.all

最新更新