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

发布于 2024-11-17 13:10:09 字数 298 浏览 0 评论 0原文

我正在编写一个 node.js 应用程序,其中 stdout 通过管道传输到文件。我正在用 console.log 编写所有内容。一段时间后,我的应用程序达到 1GB 限制并停止。有趣的是,如果我使用 console.error 而不是 console.log,内存使用率保持较低并且程序运行良好。所以看起来 Node.js 无法刷新标准输出流,所有内容都保存在内存中。我想让 stderr 不出现错误。

我的问题是:

有没有办法将阻塞写入标准输出?或者至少,我可以使用对标准输出的回调进行写入,这样我就可以确保我不会写太多?

谢谢!

I'm writing a node.js application which stdout is piped to a file. I'm writing everything with console.log. After a while my Application reaches the 1GB Limit and stops. The interesting thing is, that if I use console.error instead of console.log, the memory usage keeps low and the programm runs fine. So it looks like node.js can't flush the stdout stream and everything is kept in memory. I wanna keep stderr free for errors.

My Question is:

Is there a way to write blocking into stdout? Or at least, can I write with a callback to stdout, so I can ensure I'm writing not too much?

thx!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

错々过的事 2024-11-24 13:10:09

如果你真的想要同步写入标准输出,你可以这样做:

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

If you really really want synchronous writes to stdout you can do:

var fs = require('fs');
fs.writeSync(1, "Foo\n");
fs.fsyncSync(1);
嗳卜坏 2024-11-24 13:10:09

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

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

Write using process.stdout.write, the return value is whether data got buffered. If it's true, continue writing when process.stdout emits the drain event.

If you want your code to look sync, use streamlinejs as described here: Node.js stdout flush

不必你懂 2024-11-24 13:10:09

出现该问题(内存使用量爆炸)的原因可能是您的程序创建输出的速度比显示输出的速度快。因此你想限制它。您的问题要求“同步输出”,但实际上可以通过使用纯粹的“异步”(*) 代码来解决问题。

(*注意:在这篇文章中,术语“异步”用于“javascript-单线程”的含义。这与传统的“多线程”含义不同,后者完全不同)

这个答案展示了如何将“异步”代码与 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();

运行上面的代码(情况 2)会显示内存使用量仍然没有爆炸(与情况 1 相同),因为 < code>logger 关联的切片已暂停,但 CPU 使用率仍然如此,因为 essentialWorker 切片没有暂停 - 这很好(想想新冠肺炎)。

相比之下,同步解决方案也会阻塞 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 
...

可能会出现。

The problem (exploding memory usage) probably occurs because your program is creating output faster than it can be display. Therefore you want to throttle it. Your question requests "synchronous output", but actually the problem can be solved by using purely "asynchronous"(*) code.

(* NOTE: In this post the term "asynchronous" is used in the "javascript-single-thread" sense. That differs from the conventional "multi-thread" sense which an entirely different kettle of fish).

This answer shows how "asynchronous" code can be used with Promises to prevent memory usage from exploding by "pausing" (as opposed to blocking) execution until the write output has been successfully flushed. This answer also explains how an asynchronous code solution can be advantageous compared to a synchronous code solution.

Q: "Paused" sounds like "blocked", and how can asynchronous code possibly "block"? That's an oxymoron!

A: It works because the the javascript v8 engine pauses (blocks) execution of only the single code slice await an asynchronous promise to complete, while permitting other code slices to execute in the meantime.

Here is an asynchronous write function (adapted from 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);
  });
}

It can be called from an asynchrous function in your source code, e.g.
case 1

async function main() {
  while (true)
    await streamWriteAsync(process.stdout, 'hello world\n')
}
main();

Where main() is the only function called from the top level. The memory usage will not explode as it would if calling console.log('hello world');.

More context is required to clearly see the advantage over a true synchronous write:
case 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();

Running the above code (case 2) would show that the memory use is still not exploding (same as case 1) because the logger associated slice was paused, but the CPU usage was still because as the essentialWorker slice was not paused - which is good (think COVID).

In comparison, a synchronous solution would also block the essentialWorker.

What happens with multiple slices calling 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();

In this case (case 3), the memory usage is bound, and the essentialWorker CPU usage is high, the same as in case 2. Individual lines of hello world and goodbye world would remain atomic, but the lines would not alternate cleanly, e.g.,

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

could appear.

哀由 2024-11-24 13:10:09

编辑:正如评论者指出的,这个解决方案有问题。 printResolver 可以做成一个数组,但使用顶级解决方案要容易得多。

同步打印函数也可以使用异步/等待,与管道(又名 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;
        });
    }
}

Edit: as indicated by commenter, this solution has a problem. printResolver could be made into an array, but using top solution is much easier

A synchronous print function that also works with pipes aka FIFO's, using Async/await.
Make sure you always call "print" with "await print"

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;
        });
    }
}
维持三分热 2024-11-24 13:10:09

不。

您想要做的是当输出已满时pause()您的输入,就像pump()方法一样,然后resume()当你有空间写的时候就可以了。如果没有,您的流程就会变得非常庞大。

不过,您可能想使用更直接的 outputStream 内容,或者 write() 调用,而不是 console.log()

Don't.

What you want to do is pause() your input when the output is full, like the pump() method does, then resume() it when you've got space to write. If not, your process balloons out to gargantuan size.

You probably want to use the more direct outputStream stuff for that, though, or the write() call, not console.log().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文