我有一个代码中的情况,其中有一个巨大的函数,逐行解析记录,验证和写入另一个文件.
如果文件中存在错误,它会调用另一个拒绝记录的函数并写入拒绝原因.
由于程序中存在内存泄漏,它会与SIGSEGV崩溃.从崩溃的位置"重新启动"文件的一种解决方案是将最后处理的记录写入简单文件.
为此,需要将处理循环中的当前记录号写入文件.如何确保循环内的文件上的数据被覆盖?
使用fseek首先在循环中定位/回放会降低性能吗?
记录的数量可以很多,有时(高达500K).
谢谢.
编辑:内存泄漏已得到修复.建议将重启解决方案作为额外的安全措施,并提供重启机制以及SKIP n记录解决方案.很抱歉没有提到它.
遇到这种问题时,您可以采用以下两种方法之一:
您建议的方法:对于您阅读的每条记录,将记录号(或ftell
输入文件上返回的位置)写出到单独的书签文件中.为了确保你从中断的地方恢复,不要引入重复的记录,你必须fflush
在每次写入之后(对两者bookmark
和输出/拒绝文件).这种和无缓冲的写操作一般会减慢典型(无故障) )场景显着.为了完整起见,请注意您有三种写入书签文件的方法:
fopen(..., 'w') / fwrite / fclose
- 非常慢
rewind / truncate / fwrite / fflush
- 略快一点
rewind / fwrite / fflush
- 有点快 ; 您可以跳过,truncate
因为记录号(或ftell
位置)将始终与前一个记录号(或ftell
位置)一样长或长,并且将完全覆盖它,前提是您在启动时截断文件一次(这将回答您的原始问题)
假设大多数情况下一切顺利; 在失败后恢复时,只需计算已输出的记录数(正常输出加拒绝),并从输入文件中跳过相同数量的记录.
这样可以非常快速地保持典型(无故障)方案,而不会在故障后恢复情况下显着降低性能.
您不需要fflush
文件,或者至少不需要这样.fflush
在切换到写入fflush
拒绝文件之前,仍然需要主输出文件,然后在切换回写入主输出文件之前拒绝文件(对于500k记录输入可能需要几百或几千次.)只需删除从输出/拒绝文件的最后一个未终止的行,到该行的所有内容将是一致的.
我强烈推荐方法#2.与方法#2所需的任何附加(缓冲)读取相比,方法#1所引入的写入(三种可能中的任何一种)都非常昂贵(fflush
可能需要几毫秒;乘以500k并得到分钟 - 而计算数量500k记录文件中的行只需几秒钟,而且文件系统缓存正在使用,而不是针对您.)
编辑 只是想澄清实现方法2所需的确切步骤:
当写入输出并分别拒绝文件时,只需要在从写入文件切换到写入另一个文件时进行刷新.请考虑以下场景,说明执行这些文件刷新切换的必要性:
假设您将1000条记录写入主输出文件,然后
你必须在拒绝文件中写入1行,而不是先手动刷新主输出文件
你再向主输出文件写200行,而不是先手动刷新拒绝文件
运行时会自动刷新主输出文件,因为您在主输出文件的缓冲区中累积了大量数据,即1200条记录
但是运行时还没有自动将拒绝文件刷新到磁盘,因为文件缓冲区只包含一条记录,这个记录不足以自动刷新
你的程序此时崩溃了
你恢复并计算主输出文件中的1200条记录(运行时为你刷出了这些记录),但拒绝文件中的0(!)记录(未刷新).
您继续处理记录#1201的输入文件,假设您只有1200条记录成功处理到主输出文件; 被拒绝的记录将丢失,并且将重复第1200个有效记录
你不想要这个!
现在考虑切换输出/拒绝文件后手动刷新:
假设您将1000条记录写入主输出文件,然后
您遇到一个属于拒绝文件的无效记录; 最后一条记录是有效的; 这意味着您正在切换到写入拒绝文件:在写入拒绝文件之前刷新主输出文件
你现在写1行到rejects文件,然后
你遇到一个属于主输出文件的有效记录; 最后一条记录无效; 这意味着您将切换到写入主输出文件:在写入主输出文件之前刷新rejects文件
你再向主输出文件写200行,而不是先手动刷新拒绝文件
假设运行时没有为您自动刷新任何内容,因为自主输出文件上次手动刷新以来缓冲的200条记录不足以触发自动刷新
你的程序此时崩溃了
您在主输出文件中恢复并计算1000个有效记录(您在切换到拒绝文件之前手动刷新了这些记录),并在拒绝文件中记录了1个记录(您在切换回主输出文件之前手动刷新).
您正确地恢复处理记录#1001处的输入文件,这是无效记录之后的第一个有效记录.
您重新处理接下来的200个有效记录,因为它们没有刷新,但您没有丢失记录,也没有重复记录
如果您对运行时的自动刷新之间的间隔不满意,您也可以每100个或每1000个记录手动刷新一次.这取决于处理记录是否比刷新更昂贵(如果处理更昂贵,经常刷新,可能在每个记录之后,否则只有在输出/拒绝之间切换时才会刷新.)
从失败中恢复
打开输出文件和拒绝文件进行读取和写入,并从读取和计数每条记录开始(比如说records_resume_counter
)直到你到达文件末尾
除非您在输出的每条记录之后刷新,否则还需要对输出和拒绝文件中的最后一条记录执行一些特殊处理:
在从中断的输出/拒绝文件中读取记录之前,记住您在所述输出/拒绝文件中的位置(使用ftell
),让我们称之为last_valid_record_ends_here
阅读记录.验证记录不是部分记录(即运行时没有将文件刷新到记录的中间).
如果每行有一条记录,可以通过检查记录中的最后一个字符是回车符还是换行符(\n
或`r`)来轻松验证
如果记录完成,则递增记录计数器并继续下一条记录(或文件结尾,以先到者为准).
如果记录是部分的,则fseek
返回last_valid_record_ends_here
并停止从此输出/拒绝文件中读取; 不要增加柜台; 继续下一个输出或拒绝文件,除非你已经完成了所有这些
打开输入文件以进行读取并records_resume_counter
从中跳过记录
继续处理并输出到输出/拒绝文件; 这将自动附加到输出/拒绝文件,在该文件中,您不会读取/计算已处理的记录
如果必须对部分记录刷新执行特殊处理,则输出的下一条记录将覆盖上一次运行(at last_valid_record_ends_here
)的部分信息- 您将没有重复,垃圾或丢失记录.