问题(内存使用爆炸)可能是因为您的程序创建输出的速度比它能够显示的速度快。因此,您需要进行限制。您的问题要求“同步输出”,但实际上可以通过使用纯“异步”(*)代码来解决问题。
(*注意:在本帖中,“异步”一词是以“javascript-single-thread”的意义使用的。这与传统的“多线程”意义不同,是完全不同的事情)。
本答案展示了如何使用Promise来使用“异步”代码,通过“暂停”(而不是阻塞)执行,直到写出的输出已成功刷新,从而防止内存使用爆炸。本答案还解释了与同步代码解决方案相比,异步代码解决方案的优势。
问:“暂停”听起来像“阻塞”,异步代码怎么可能会“阻塞”?这是个自相矛盾的说法!
答:这是因为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();
运行上述代码(case 2)会发现内存使用仍未爆炸(与case 1相同),因为与logger
相关的切片已被暂停,但CPU使用率仍在,因为essentialWorker
切片未被暂停 - 这是好的(想想COVID)。
相比之下,同步解决方案也会阻塞essentialWorker
。
多个切片调用streamWrite
会发生什么?
case 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();
在这种情况下(
第三种情况),内存使用已经到达极限,
essentialWorker
的 CPU 使用率很高,与
第二种情况相同。
hello world
和
goodbye world
的单独行仍然是原子的,但是这些行不会干净地交替出现,例如:
...
hello world
hello world
goodbye world
hello world
hello world
...
可能会出现。