如何用node.js在stdout中编写阻塞?

时间:2011-06-24 16:43:45

标签: javascript node.js stdout stderr

我正在编写一个node.js应用程序,它将stdout传送到一个文件。我正在使用console.log编写所有内容。一段时间后,我的应用程序达到1GB限制并停止。有趣的是,如果我使用console.error而不是console.log,内存使用率会保持较低并且程序运行正常。所以看起来node.js无法刷新stdout流,所有内容都保存在内存中。我想让stderr免费提供错误。

我的问题是:

有没有办法将阻塞写入stdout?或者至少,我可以写一个回调到stdout,所以我可以确保我写的不是太多吗?

THX!

5 个答案:

答案 0 :(得分:12)

如果你真的想要同步写入stdout,你可以这样做:

var fs = require('fs');
fs.writeSync(1, "Foo\n");
fs.fsyncSync(1);

答案 1 :(得分:6)

使用process.stdout.write写入,返回值是数据是否被缓冲。如果是,请在process.stdout发出drain事件时继续撰写。

如果您希望代码看起来同步,请按照此处所述使用streamlinejs:Node.js stdout flush

答案 2 :(得分:1)

别。

当输出已满时,您想要做的是pause()输入,就像pump()方法那样,然后resume()当您有空间写入时。如果没有,你的过程会膨胀到庞大的大小。

您可能希望使用更直接的outputStream内容,或write()来电,而不是console.log()

答案 3 :(得分:0)

使用Async / await,同步打印功能也可以与管道(也称为FIFO)一起使用。 确保始终将“打印”与“等待打印”一起调用

let printResolver;
process.stdout.on('drain', function () {
    if (printResolver) printResolver();
});

async function print(str) {
    var done = process.stdout.write(str);
    if (!done) {
        await new Promise(function (resolve) {
            printResolver = resolve;
        });
    }
}

答案 4 :(得分:0)

问题(内存使用量激增)可能是由于程序创建输出的速度快于显示速度而导致的。因此,您想限制它。您的问题要求“同步输出”,但实际上可以使用纯“异步”(*)代码来解决问题。

(*注意:在本文中,“异步”一词的含义是“ javascript-单线程”,这与传统的“多线程”含义有所不同,后者是完全不同的水壶鱼)。

此答案显示了“异步”代码如何与Promises一起使用,以通过“暂停”执行(而不是阻塞)来阻止内存使用量激增,直到成功清除写入输出为止。该答案还说明了与异步代码解决方案相比,异步代码解决方案有何优势。

问:“暂停”听起来像“被阻止”,异步代码怎么可能“被阻止”?那是矛盾的事!

A:之所以起作用,是因为javascript v8引擎仅暂停(阻止)执行 单个代码片,以等待异步承诺完成,同时允许其他代码片同时执行。 / p>

这是异步写入功能(改编自here)。

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关联的切片暂停,但是CPU使用率仍然是因为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 CPU使用率很高,与情况2 相同。 hello worldgoodbye world的各个行将保持原子状态,但这些行将不会清晰地交替显示,例如,

...
hello world 
hello world 
goodbye world 
hello world 
hello world 
...

可能会出现。