多个 fs.write 追加到同一个文件可以保证执行顺序吗?

Posted

技术标签:

【中文标题】多个 fs.write 追加到同一个文件可以保证执行顺序吗?【英文标题】:Can Multiple fs.write to append to the same file guarantee the order of execution? 【发布时间】:2017-03-10 14:57:57 【问题描述】:

假设我们有这样一个程序:

// imagine the string1 to string1000 are very long strings, which will take a while to be written to file system
var arr = ["string1",...,"string1000"]; 
for (let i = 1; i < 1000; i++) 
  fs.write("./same/path/file.txt", arr[i], flag: "a");

我的问题是,will string1 to string1000 be gurantted to append to the same file in order?

由于 fs.write 是异步函数,我不确定每次调用 fs.write() 是如何真正执行的。我假设对每个字符串的函数调用应该放在another thread 中的某个位置(比如callstack?),一旦前一个调用完成,就可以执行下一个调用。

我不确定我的理解是否准确。

编辑 1

正如在 cmets 和答案中一样,我看到 fs.write 对于多次写入同一个文件而不等待 callback 是不安全的。但是 writestream 呢?

如果我用下面的代码,能保证写的顺序吗?

// imagine the string1 to string1000 are very long strings, which will take a while to be written to file system
var arr = ["string1",...,"string1000"]; 
var fileStream = fs.createWriteFileStream("./same/path/file.txt",   "flags": "a+" );
for (let i = 1; i < 1000; i++) 
  fileStream.write(arr[i]);

fileStream.on("error", () => // do something);
fileStream.on("finish", () => // do something);
fileStream.end();

任何 cmets 或更正都会有所帮助!谢谢!

【问题讨论】:

如果你的意思是fs.writeFile(),那么文档指出“......在同一个文件上多次使用fs.writeFile而不等待回调是不安全的”. 请注意,由于 node.js 中缓冲的工作方式,不等待回调不会影响顺序或写入。不安全的是缓冲区溢出的可能性。所以风险在于丢失写入,而不是无序写入。 @slebetman,感谢您的回复。但是这里的缓冲到底是什么意思呢? @Lubor:核心 node.js 通过为每个打开的文件生成一个线程来管理文件/磁盘 I/O。当您写入时,您实际上并没有写入文件。相反,您要做的是向此 I/O 线程发送消息。所以这个 I/O 线程需要将此消息存储在 RAM 中的某个位置。这是 I/O 缓冲区。我相信它的大小在编译时是固定的。然后,只要文件可写,线程就会执行适当的异步 I/O 循环,将数据从该缓冲区写入磁盘(当文件的操作系统写入缓冲区为空时,您的操作系统再次不会写入磁盘,而是以类似的方式缓冲) @Lubor:从我在回答类似问题时读到的代码中,我可以看到进入此 I/O 缓冲区并从该缓冲区中取出以写入磁盘的字符串的顺序得到保证。缓冲区无法重新排列,因此您写入的顺序就是将写入磁盘的顺序。但如果缓冲区已满,您的写入将被忽略。 【参考方案1】:

docs 这么说

请注意,在同一个文件上多次使用fs.write 而不等待回调是不安全的。对于这种情况,强烈建议使用 fs.createWriteStream。

使用流是有效的,因为流本质上保证写入它们的字符串的顺序与从中读出的顺序相同。

var stream = fs.createWriteStream("./same/path/file.txt");
stream.on('error', console.error);
arr.forEach((str) =>  
  stream.write(str + '\n'); 
);
stream.end();

另一种仍然使用fs.write但也确保事情发生的方法是使用promise来维护顺序逻辑。

function writeToFilePromise(str) 
  return new Promise((resolve, reject) => 
    fs.write("./same/path/file.txt", str, flag: "a", (err) => 
      if (err) return reject(err);
      resolve();
    );
  );


// for every string, 
// write it to the file, 
// then write the next one once that one is finished and so on
arr.reduce((chain, str) => 
  return chain
   .then(() => writeToFilePromise(str));
, Promise.resolve());

【讨论】:

那么 createWriteFileStream 呢?如果我使用filestream,我们可以保证顺序吗? 谢谢!但是你是说 stream.write(str) 是同步操作吗?我一直认为这是异步的。 @Lubor 仅供参考:根据定义,流保证写入它的顺序与“输出”的顺序相同(在这种情况下,写入文件)。 @tcooc 哇,这个定义很有意义!谢谢! @Lubor 我的错误,我的措辞是错误的。 stream.write 是异步的。【参考方案2】:

您可以使用节点的读/写锁定来同步对文件的访问,请参阅以下示例,您可以阅读documentation

var ReadWriteLock = require('rwlock');

var lock = new ReadWriteLock();

lock.writeLock(function (release) 
  fs.appendFile(fileName, addToFile, function(err, data) 
    if(err) 
      console.log("write error"); //logging error message
    else    
      console.log("write ok");

    release(); // unlock
   );    
);

【讨论】:

【参考方案3】:

我遇到了同样的问题,并为我的项目编写了一个 NPM 包来解决它。它的工作原理是将数据缓冲在一个数组中,然后等待事件循环结束,在对fs.appendFile 的一次调用中连接和写入数据:

const SeqAppend = require('seqappend');

const writeLog = SeqAppend('log1.txt');

writeLog('Several...');
writeLog('...logged...');
writeLog('.......events');

【讨论】:

以上是关于多个 fs.write 追加到同一个文件可以保证执行顺序吗?的主要内容,如果未能解决你的问题,请参考以下文章

nodejs模块——fs模块 使用fs.write读文件

了解来自多个进程的并发文件写入

node中fs模块 - fs.open() fs.read() fs.write() fs.close()

fs.write 混淆为啥只需要 5 个参数?

fs.write 混淆为啥只需要 5 个参数?

学习node4_write方法