我有一个Perl脚本,它每三秒向现有文件追加一行内容。同时,有一个C++应用程序会从该文件中读取。
问题在于,应用程序在脚本完成并关闭文件句柄后开始读取文件。为了避免这种情况,我想在每次追加新行后进行强制刷新。如何实现?
我有一个Perl脚本,它每三秒向现有文件追加一行内容。同时,有一个C++应用程序会从该文件中读取。
问题在于,应用程序在脚本完成并关闭文件句柄后开始读取文件。为了避免这种情况,我想在每次追加新行后进行强制刷新。如何实现?
$| = 1
。 - talkloudTL/DR: 使用 IO::Handle
和 flush
方法,例如:
use IO::Handle;
$myfile->flush();
Perl文件句柄上的内部缓冲区。其他程序在数据离开此缓冲区之前无法看到数据。
“脏”文件块的文件系统级缓冲。其他程序仍然可以看到这些更改,它们似乎已经“写入”,但如果操作系统或计算机崩溃,它们将丢失。
写入的磁盘级回写缓冲。操作系统认为这些已写入磁盘,但实际上磁盘只是将它们存储在驱动器上的易失性内存中。如果操作系统崩溃,数据不会丢失,但如果电源失败,则可能会丢失,除非磁盘可以首先将其写出。这是廉价消费者SSD的一个大问题。
当涉及SAN、远程文件系统、RAID控制器等时,情况变得更加复杂。如果通过管道进行写入,还需要考虑管道缓冲区。
如果您只想清空Perl缓冲区,您可以关闭文件,打印一个包含"\n"
的字符串(因为似乎Perl在换行时刷新),或者使用IO::Handle
的flush
方法。binmode
或操作$|
使文件句柄无缓冲。这与清空缓冲句柄不同,因为将一堆缓冲写入队列,然后执行单个刷新的性能成本比写入无缓冲句柄要低得多。fsync()
这样的系统调用,以O_DATASYNC
模式打开文件,或使用众多其他选项。这是非常复杂的,正如PostgreSQL拥有自己的工具来测试文件同步方法所证明的那样。来自《Perl常见问题解答》手册:
$old_fh = select(OUTPUT_HANDLE);
$| = 1;
select($old_fh);
如果你只想清空标准输出(stdout),你可以尝试这样做:
$| = 1;
但是请查看FAQ以了解一个模块,它可以为您提供更易于使用的抽象,比如IO::Handle
。
select((select(OUTPUT_HANDLE),$|=1)[0])
(摘自 https://dev59.com/gHVC5IYBdhLWcg3wvT7g )。请参见该线程中有关此方法的其他考虑、优点和问题。 - msb这里是答案 - 真正的答案。
停止在进程的整个生命周期中保持此文件的打开文件句柄。
开始将您的文件追加操作抽象为一个子程序,该子程序以追加模式打开文件,写入内容并关闭文件。
# Appends a new line to the existing file
sub append_new_line{
my $linedata = shift;
open my $fh, '>>', $fnm or die $!; # $fnm is file-lexical or something
print $fh $linedata,"\n"; # Flavor to taste
close $fh;
}
fsync()
或你的操作系统等价的文件描述符。否则,在操作系统崩溃或断电时,数据可能会丢失。 - Craig Ringer在PerlDoc中有一篇关于此的文章:如何刷新/取消缓冲输出文件句柄?为什么我必须这样做?
两种解决方案:
$|
取消输出文件句柄的缓冲。IO::Handle
或其子类之一,请调用autoflush方法。为了自动刷新输出,您可以像其他人描述的那样在输出到文件句柄之前设置autoflush/$|
。
如果您已经输出到文件句柄并且需要确保它到达物理文件,则需要使用IO::Handle flush
和sync
方法。
真正正确的答案是使用:
$|=1; # Make STDOUT immediate (non-buffered)
虽然这是你问题的一个原因,但同样的问题的另一个原因是:“还有一个C++应用程序从那个文件中读取。”
编写能够正确读取正在增长的文件的C++代码非常复杂,因为当你的“C++”程序到达文件末尾时,它会遇到EOF...(你不能在没有严重额外技巧的情况下读取文件的结尾) - 你必须使用IO阻塞和标志进行一堆复杂的操作来正确监视文件(就像Linux的“tail”命令的工作方式一样)。
对于那些正在寻找一种解决方案,以便在使用会话文件(*.cse)时将输出逐行刷新到文件中的Ansys CFD Post,这是我唯一有效的解决方案:
! $file="Test.csv";
! open(OUT,"+>>$file");
! select(OUT);$|=1; # This is the important line
! for($i=0;$i<=10;$i++)
! {
! print out "$i\n";
! sleep(3);
! }
sleep(3);
仅用于演示目的。use IO::Handle;
不需要。
binmode $filehandle
,它将设置:raw
格式,并(作为副作用)进行刷新。它的效果很好。无论如何,如果它在Unix上,您可以创建一个命名管道到文件系统,然后直接写入它。 - TrueY/dev/stdin
如果需要文件名而愚蠢)中读取,并检查它是否能够逐行读取 - 如果使用块缓冲读取,则在写入端无法进行任何操作。 - reinierpost