写文件操作探微

来源:互联网 发布:压缩比最高的软件 编辑:程序博客网 时间:2024/06/08 13:16

多进程读写同一个文件的问题

不考虑文件内容的错乱,多进程是可以同时读写一个文件的。当一个进程在写,读的进程能否读到最新的内容,取决于最新的内容是否真正写到了磁盘上。

写缓存与写磁盘

磁盘缓存是物理内存的一部分,专门供操作系统用作读写磁盘的缓冲之用。磁盘缓存与“硬盘自带的缓存”是不一样的概念,它的大小是可以动态设置的,而不像硬盘缓存在出厂的时候固定就是32M或64M。
我们通常用到的写文件API,其实是写到磁盘缓存上,可用python语言做一个实验:

if opt == '-w':    with open('1.txt', 'w') as writer:        writer.write('hehe\n')        time.sleep(10)elif opt == '-r':    with open('1.txt') as fp:        for line in fp:            line = line.rstrip()            print line

我们在用-w选项写hehe之后不会立刻关闭文件,而是sleep了10s,方便使用-r选项去读文件,读的时候我们发现,除非文件关闭,否则读不出任何内容。这印证了前面的说法,hehe字符串在文件关闭前只在磁盘缓存里,还未真正写到磁盘上,所以读进程无法读出。

如何确保写到磁盘上而不只是磁盘缓存里呢?python文档给出了建议:

file.flush() Flush the internal buffer.Noteflush() does not necessarily write the file’s data to disk. Use flush() followed by os.fsync() to ensure this behavior.

文档建议我们flush+fsync,确保内容确实更新到了磁盘。
fsync的帮助也指出了这一点:

os.fsync(fd) Force write of file with filedescriptor fd to disk. On Unix, this calls the native fsync() function; on Windows, the MS _commit() function.If you’re starting with a Python file object f, first do f.flush(), and then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk.

所谓的“内部缓存”就是磁盘缓存,强制更新到磁盘,linux下用的是大家熟知的fsync,windows下则是_commit函数。

我们将写的代码改成:

if opt == '-w':    with open('1.txt', 'w') as writer:        writer.write('hehe\n')        writer.flush()        os.fsync(writer.fileno())        time.sleep(10)

果然,读进程就能在文件尚未关闭时读到hehe字符串了。
但是,fsync是要慎用的,因为每条内容都强制刷新到磁盘,虽然非常可靠,却会带来性能的急剧下降,我们可以在上述例子的基础上改成写10万条字符串,对比“普通写”与“fsync写”的效率,会发现后者的耗时是前者的数千倍甚至是上万倍!这也正是redis的AOF日志虽然提供了fsync级别的磁盘同步却不建议我们使用的原因(也因此redis的日志做不到绝对的单点可靠)。

这里还有一个疑问,按python的文档,flush并不一定能将最新的内容更新到磁盘上,我们查看java file API的文档,发现也有类似的说法。这是为何?我个人的猜测,这里可能存在一个flush门限的问题,即磁盘缓存里的数据未达到一定的比例,操作系统不会将其写入磁盘驱动器,flush这个API只是建议操作系统可以去清空磁盘缓存,而非fsync那样的强制动作。

进程内缓存与磁盘缓存

进程内缓存指的是我在写磁盘缓存前,在自己的程序里再做一个缓存,将多条消息累积到一定的大小,再一次提交给磁盘缓存,这样能提升写的效率。java里一般要在FileWriter之上再套一层BufferedWriter写入,就是这个用途,实测下来,也能有一倍的效率提升。
python语言里没有BufferedWriter,对于10万条字符串的写可以考虑别的方法,比如我们可以每500条拼成一个大的字符串再做写入,实测也有一倍的效率提升。
不过,如同前面的“普通写”与“fsync写”一样,效率的提升不是全无代价,它往往伴随着可靠性的降低。进程内缓存是属于某个进程的,一旦该进程突然core掉,进程内缓存就会丢失,从用户层面看来,就是我明明已经write好了的数据,很可能并未写到磁盘里。相比之下,磁盘缓存就更可靠一些,因为它是由操作系统管理的,与进程无关,除非是机器断电,否则它不会丢失数据,也就是说,即使我的进程core掉,之前write的内容依然可以安全到达磁盘上。当然,如果追求极致的可靠而不恤性能,fsync是最好的选择。

原创粉丝点击