Как не перенасыщать libuv задачами при использовании операций ввода-вывода

#javascript #typescript #asynchronous #libuv

#javascript #typescript #асинхронный #libuv

Вопрос:

Я использую Typescript и, следовательно libuv , для выполнения любых операций ввода-вывода. В моем конкретном сценарии я беру хэш отпечатка данного файла. Чтобы подчеркнуть здесь мой вопрос, рассмотрите входной файл как файл объемом 1 ТБ. Чтобы получить отпечаток файла, я мог бы открыть файл через поток файлов и обновить хэш:

 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 асинхронных операций ввода-вывода в фоновом режиме? Или libuv автоматически ставит их в очередь пакетами, чтобы избежать перенасыщения запросов ввода-вывода?

Любая помощь в этой теме высоко ценится!

Ответ №1:

Я попытаюсь как можно короче изложить некоторые ключевые концепции. Я оставлю ссылки для справки ниже, чтобы вы могли проверить факты.

Обещание добавляет задачу в нечто, называемое очередью микрозадач. На каждой итерации цикла событий, когда стек вызовов пуст, обрабатываются задачи из очереди микрозадач. Это известно как tick . Таким образом, каждый тик будет обрабатываться несколько задач из очереди микрозадач.

Для каждого тика процесса существует значение max depth ( process.maxTickDepth ). Это указывает количество задач, которые должны быть выгружены из очереди микрозадач и помещены в стек вызовов.

Основная часть вашего алгоритма включает в себя чтение содержимого, которое является операцией ввода-вывода. Такие операции помещаются в отдельную очередь, известную как очередь макрозадач. Когда операция макрозадачи расписания завершена и у нее есть указанный фрагмент содержимого, обработчик события для операции чтения помещается в очередь в очередь микрозадач для обработки на следующем тике.

Учитывая ваш фрагмент и ограничения, если максимальная глубина равна 1000, то для вашего алгоритма для полного обновления хэша необходимо передать как минимум N / 1000 = 1000000 / 1000 = 1000 тики. Это означает, что Node.js процесс будет обрабатывать только определенное количество задач за тик.

Я надеюсь, что это дает вам понимание, которое вы ищете.

Ссылки:

Node.js Under The Hood #3 — Глубокое погружение в цикл событий

Документация MDN по Promise.all

Комментарии:

1. Спасибо! Это потрясающее понимание! Я просмотрю ваши ссылки, поэтому, если я вас правильно понимаю, libuv буду обрабатывать макрозадачи самостоятельно, поэтому не о чем беспокоиться, если я выполню несколько async readFile(..) операций, libuv поскольку это просто ограничит истинное параллельное выполнение внутренними ограничениями и будет выполнять их пакетно. Я правильно резюмировал это?

2. Я думаю, вы это сделали. Мое собственное понимание и знание всех материалов, с которыми я сталкивался до сих пор, предполагает это.