gpt4 book ai didi

javascript - 用async等待正确地附加到文件的好模式是什么?

转载 作者:太空宇宙 更新时间:2023-11-03 22:56:45 26 4
gpt4 key购买 nike

重复调用fs.appendFile()之类的函数是否安全?您可以考虑一个人为的示例,当使用child_process.spawn和“ for-async-of”循环以js实现tee时。我们以块的形式获取文件数据,并希望将它们附加到文件中并执行其他处理。如果appendFile()被承诺并被await,则执行将被暂停,直到我们知道文件已被写入为止,这可能会浪费时间。但是,如果我不等待,我将不再确定我潜在地快速重复调用appendFile会导致隔行扫描输出文件。

节点fs库是否为我执行文件锁定?这样做的有效方法是什么?我是否应该编写一个帮助程序类以将工作分流到其自己的异步附加循环中?

到目前为止,这是我的测试,这令人鼓舞,尽管似乎永远不会得出结论:

#!/usr/bin/env node                                                             
const spawn = require('child_process').spawn;
const fs = require('fs');
const util = require('util');
const start = new Date();
const proc = spawn('seq 1 200000', { shell:true });
proc.stdout.setEncoding('utf8');
(async()=>{
for await (const data of proc.stdout) {
console.log(new Date() - start, `got ${data.length} chars of stdout`);
util.promisify(fs.appendFile)('junk', data);
};
})();


输出:

5 'got 8192 chars of stdout'
7 'got 65536 chars of stdout'
7 'got 65536 chars of stdout'
7 'got 53248 chars of stdout'
8 'got 65536 chars of stdout'
8 'got 65536 chars of stdout'
8 'got 49152 chars of stdout'
9 'got 65536 chars of stdout'
9 'got 65536 chars of stdout'
9 'got 65536 chars of stdout'
9 'got 24576 chars of stdout'
9 'got 65536 chars of stdout'
9 'got 40960 chars of stdout'
10 'got 49152 chars of stdout'
10 'got 32768 chars of stdout'
10 'got 32768 chars of stdout'
10 'got 32768 chars of stdout'
10 'got 32768 chars of stdout'
10 'got 32768 chars of stdout'
11 'got 32768 chars of stdout'
11 'got 32768 chars of stdout'
11 'got 32768 chars of stdout'
11 'got 24576 chars of stdout'
11 'got 24576 chars of stdout'
11 'got 32768 chars of stdout'
12 'got 65536 chars of stdout'
12 'got 65536 chars of stdout'
13 'got 64191 chars of stdout'


diff junk <(sort -n junk)表示输出正确(没有隔行扫描)。

当我在追加调用之前添加 await时,执行写操作所花费的时间增加了大约30%。

我想根据结果,即使跳过该命令看起来很安全,我也将只等待appendFile调用。性能影响很小。

最佳答案

理论上,您的代码受竞争条件的影响,因为在不同的fs.appendFile()操作中进行的各种写入可能会发生冲突,并且可能同时进行,因为您的for循环在完成所有fs.appendFile()操作之前便已完成。因此,您将同时进行多个fs.appendFile()操作。这不是编写代码的安全方法。如果要按特定顺序序列化写操作,以使它们不会发生冲突,则应保证这是通过编写代码的方式发生的,而不是靠计时全部如何完成或库代码的内部情况如何而产生的运气被写。

我已经在一个测试应用程序中证明fs.appendFile()不会获得独占访问权,并且不会锁定文件,并且同一程序中的多个fs.appendFile()写入操作可能会相互覆盖,或者它们可能以不可预测的顺序交错。这意味着您要冒险同时进行多个fs.appendFile()操作。我的猜测是,由于磁盘的写入速度和一次处理大块数据的能力,您很幸运。如果底层系统必须将写入分为多个块,则您更有可能发生冲突。

此外,运气还可能因操作系统,文件系统类型以及文件系统负载和性能而异。

实际上,我已经展示了即使是这样的简单循环,其中每次写入的数据也只是其中的40行:

'0123456789abcdefghijhklmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n'


并且,此代码:

const numIterations = 100;
for (let i = 0; i < numIterations; i++) {
fs.appendFile("output.txt", `(${i}):` + testData, function(err) {
if (err) {
console.log(i, err);
process.exit(1);
}
});
}


不会按顺序写入所有数据。我按以下顺序编写了循环的前四个迭代:

(0), (2), (3), (1)


然后,稍后

(10), (11), (9)


乱序。



如果使用 for await (...)并使 for循环在每次迭代之间只等待一小段时间,则写入将再次按顺序进行(毫不奇怪)。但是,我认为这只是表明它本质上是一种竞争条件,只是有时候您可以创建一些条件,而这些条件通常不会咬住您。那仍然不是正确的编码方式。



另外,此代码中的promisify内容没有做任何有用的事情:

#!/usr/bin/env node                                                             
const spawn = require('child_process').spawn;
const fs = require('fs');
const util = require('util');
const start = new Date();
const proc = spawn('seq 1 200000', { shell:true });
proc.stdout.setEncoding('utf8');
(async()=>{
for await (const data of proc.stdout) {
console.log(new Date() - start, `got ${data.length} chars of stdout`);
util.promisify(fs.appendFile)('junk', data);
};
})();


这段代码可能没有按照您的想象做。 util.promisify(fs.appendFile)在那里没有任何用处,因为您不必等待调用它时返回的承诺。因此,它与仅调用 fs.appendfile()完全没有区别,除非您遇到错误,否则会抱怨未处理的拒绝。

如果您执行 await这样的承诺,那么它将使代码安全,因为 await将强制 for循环等待下一个迭代开始之前的一个操作完成。



现在,关于您的一些问题?


  节点fs库是否为我执行文件锁定?


不,不是的。在Windows 10(我测试过的地方)上,当同一应用程序中的某些其他代码试图写入数据被覆盖的同一文件时,可能会导致数据冲突。我什至可以得到相同的循环,以使它们混乱无序。


  这样做的有效方法是什么?


对于此类问题,我的建议是将输入通过管道传递到输出流。打开流一次,然后将输入通过管道传递给它。完全跳过循环。每次循环时,跳过一次调用 fs.appendFile()都必须打开文件,查找到底,写入数据,然后关闭文件。效率很低。


  我是否应该编写一个帮助程序类以将工作分流到其自己的异步附加循环中?


不,只需使用流。为此专门构建了一个流。

#!/usr/bin/env node                                                             
const spawn = require('child_process').spawn;
const fs = require('fs');
const util = require('util');
const start = new Date();
const proc = spawn('seq 1 200000', { shell:true });
proc.stdout.setEncoding('utf8');
let outputStream = fs.createWriteStream('junk');
proc.stdout.pipe(outputStream);



  用async等待正确地附加到文件的好模式是什么?


如果您确实要在此处使用async / await,则可以在文件操作之前使用await。这样可以确保安全,因为您可以强制循环在开始下一次写入之前先等待一次写入完成。

#!/usr/bin/env node                                                             
const spawn = require('child_process').spawn;
const fs = require('fs');
const util = require('util');
const start = new Date();
const proc = spawn('seq 1 200000', { shell:true });
proc.stdout.setEncoding('utf8');
(async()=>{
for await (const data of proc.stdout) {
console.log(new Date() - start, `got ${data.length} chars of stdout`);
await fs.promises.appendFile('junk', data);
};
})();


其他说明:

您正在使用 for await (),因此您已经拥有最新版本的node.js。如果确实要使用分散化的文件功能,则可以仅使用内置的 fs.promises接口,而不必手动分散化 fs函数。

关于javascript - 用async等待正确地附加到文件的好模式是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60122961/

26 4 0
文章推荐: node.js - 如何确定需要的来源?
文章推荐: html - 堆叠的