使用 IO 操作时如何不让任务过度饱和 libuv
Posted
技术标签:
【中文标题】使用 IO 操作时如何不让任务过度饱和 libuv【英文标题】:How to not oversaturate libuv with tasks when using IO operations 【发布时间】:2021-04-26 09:04:45 【问题描述】:我正在使用 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 请求过饱和?
非常感谢您对此主题的任何帮助!
【问题讨论】:
【参考方案1】:我将尝试尽可能简要地总结一些关键概念。我将在下面留下链接以供参考,以便您验证事实。
Promise 将任务添加到称为微任务队列的东西中。在事件循环的每次迭代中,当调用堆栈为空时,将处理来自 Microtask Queue 的任务。这称为tick
。所以,每一个tick,都会处理来自Microtask Queue的一些任务。
对于每个进程刻度,都有一个最大深度 (process.maxTickDepth
)。这指定了要从 Microtask Queue 卸载并推入调用堆栈的任务数。
算法的主要部分涉及读取作为 I/O 操作的内容。此类操作被推入称为宏任务队列的单独队列中。当一个调度宏任务操作完成并且它具有指定的内容块时,读取操作的事件处理程序将排队到微任务队列中,以便在下一个滴答时进行处理。
鉴于您的 sn-p 和约束,如果最大深度为 1000,那么您的算法要完全更新哈希,至少需要传递 N / 1000 = 1000000 / 1000 = 1000
滴答声。这意味着 Node.js 进程每次只处理特定数量的任务。
我希望这能为您提供所需的理解。
参考资料:
Node.js Under The Hood #3 - Deep Dive Into the Event Loop
MDN Documentation on Promise.all
【讨论】:
谢谢!这是一个了不起的见解!我会查看您的链接,所以如果我理解正确,libuv
将自行处理宏任务,因此如果我在libuv
推送数十亿次async readFile(..)
操作,则无需担心,因为它只会限制真正的并行通过内部限制执行并分批执行。我总结得对吗?
我想你做到了。我自己对迄今为止遇到的所有材料的理解和知识表明了这一点。以上是关于使用 IO 操作时如何不让任务过度饱和 libuv的主要内容,如果未能解决你的问题,请参考以下文章