Проблема (взрывное использование памяти), вероятно, возникает из-за того, что ваша программа создает вывод быстрее, чем он может быть отображен. Поэтому вы хотите задушить его. Ваш вопрос требует синхронного вывода, но на самом деле проблема может быть решена с помощью чисто асинхронного (*) кода.
(* ПРИМЕЧАНИЕ. В этом посте термин асинхронный используется в смысле javascript-однопоточность. Это отличается от обычного многопоточного смысла, который представляет собой совершенно другую рыбку).< /эм>
Этот ответ показывает, как асинхронный код можно использовать с промисами, чтобы предотвратить взрывное использование памяти путем приостановки (в отличие от блокировки) выполнения до тех пор, пока вывод записи не будет успешно очищен. В этом ответе также объясняется, чем решение с асинхронным кодом может быть выгоднее по сравнению с решением с синхронным кодом.
В: Пауза звучит как блокировка, и как асинхронный код может блокировать? Это оксюморон!
О: Это работает, потому что движок javascript v8 приостанавливает (блокирует) выполнение только одного фрагмента кода в ожидании завершения асинхронного обещания, в то же время позволяя другим фрагментам кода выполняться.
Вот функция асинхронной записи (адаптировано из здесь а>).
async function streamWriteAsync(
stream,
chunk,
encoding='utf8') {
return await new Promise((resolve, reject) => {
const errListener = (err) => {
stream.removeListener('error', errListener);
reject(err);
};
stream.addListener('error', errListener);
const callback = () => {
stream.removeListener('error', errListener);
resolve(undefined);
};
stream.write(chunk, encoding, callback);
});
}
Его можно вызвать из асинхронной функции в исходном коде, например,
случай 1.
async function main() {
while (true)
await streamWriteAsync(process.stdout, 'hello world\n')
}
main();
Где main()
— единственная функция, вызываемая с верхнего уровня. Использование памяти не резко увеличится, как при вызове console.log('hello world');
.
Требуется больше контекста, чтобы ясно увидеть преимущество перед настоящей синхронной записью:
случай 2.
async function logger() {
while (true)
await streamWriteAsync(process.stdout, 'hello world\n')
}
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
function allowOtherThreadsToRun(){
return Promise(resolve => setTimeout(resolve, 0));
}
async function essentialWorker(){
let a=0,b=1;
while (true) {
let tmp=a; a=b; b=tmp;
allowOtherThreadsToRun();
}
}
async function main(){
Promise.all([logger(), essentialWorker()])
}
main();
Выполнение приведенного выше кода (случай 2) покажет, что использование памяти по-прежнему не резко возрастает (так же, как случай 1), поскольку срез, связанный с logger
, был приостановлен, но использование ЦП был по-прежнему, потому что фрагмент essentialWorker
не был приостановлен — и это хорошо (вспомните COVID).
Для сравнения, синхронное решение также блокирует essentialWorker
.
Что происходит, когда несколько фрагментов вызывают streamWrite
?
случай 3
async function loggerHi() {
while (true)
await streamWriteAsync(process.stdout, 'hello world\n')
}
async function loggerBye() {
while (true)
await streamWriteAsync(process.stdout, 'goodbye world\n')
}
function allowOtherThreadsToRun(){
return Promise(resolve => setTimeout(resolve, 0));
}
async function essentialWorker(){
let a=0,b=1;
while (true) {
let tmp=a; a=b; b=tmp;
allowOtherThreadsToRun();
}
}
async function main(){
Promise.all([loggerHi(), loggerBye(), essentialWorker()])
}
main();
В этом случае (случай 3) использование памяти ограничено, а загрузка ЦП essentialWorker
высока, как и в случай 2. Отдельные строки hello world
и goodbye world
останутся атомарными, но строки не будут чередоваться четко, например,
...
hello world
hello world
goodbye world
hello world
hello world
...
могло появиться.
person
Craig Hicks
schedule
04.11.2020