资讯专栏INFORMATION COLUMN

写文件操作探微

cppowboy / 983人阅读

摘要:写缓存与写磁盘先看下写文件操作的流程结构图磁盘缓存是物理内存的一部分,专门供操作系统用作读写磁盘的缓冲之用。这印证了前面的说法,字符串在文件关闭前只在磁盘缓存里,还未真正写到磁盘上,所以读进程无法读出。

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

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

写缓存与写磁盘

先看下写文件操作的流程结构图:

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

if opt == "-w":
    with open("1.txt", "w") as writer:
        writer.write("hehe
")
        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.

Note
flush() 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
")
        writer.flush()
        os.fsync(writer.fileno())
        time.sleep(10)

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

这里还有一个疑问,按python的文档,flush并不一定能将最新的内容更新到磁盘上,我们查看java file API的文档,发现也有类似的说法。这是为何?我个人的猜测,flush只是简单的把磁盘缓存的内容放到磁盘驱动程序的写请求队列里就返回,本质上是异步的,而fsync除了放内容到写请求队列还会等待磁盘驱动程序的返回结果,本质上是同步的。由于fsync还要额外经历:等待写请求到队列首部+磁盘驱动程序调用磁盘控制器+磁盘控制器写到物理磁盘等步骤,自然就拖慢了fsync的速度。

进程内缓存与磁盘缓存

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41073.html

相关文章

  • 文件操作探微

    摘要:写缓存与写磁盘先看下写文件操作的流程结构图磁盘缓存是物理内存的一部分,专门供操作系统用作读写磁盘的缓冲之用。这印证了前面的说法,字符串在文件关闭前只在磁盘缓存里,还未真正写到磁盘上,所以读进程无法读出。 多进程读写同一个文件的问题 不考虑文件内容的错乱,多进程是可以同时读写一个文件的。当一个进程在写,读的进程能否读到最新的内容,取决于最新的内容是否真正写到了磁盘上。 写缓存与写磁盘 先...

    plus2047 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • 后端好书阅读与推荐(续六)

    摘要:可以通过大数据生态的一系列工具生态来解决大数据问题数据分片主要有两种方式哈希和范围。哈希的问题是范围查询支持不佳,范围的问题是可能冷热数据不均。 后端好书阅读与推荐系列文章:后端好书阅读与推荐后端好书阅读与推荐(续)后端好书阅读与推荐(续二)后端好书阅读与推荐(续三)后端好书阅读与推荐(续四)后端好书阅读与推荐(续五)后端好书阅读与推荐(续六) Elasticsearch权威指南 El...

    shleyZ 评论0 收藏0
  • 后端好书阅读与推荐(续六)

    摘要:可以通过大数据生态的一系列工具生态来解决大数据问题数据分片主要有两种方式哈希和范围。哈希的问题是范围查询支持不佳,范围的问题是可能冷热数据不均。 后端好书阅读与推荐系列文章:后端好书阅读与推荐后端好书阅读与推荐(续)后端好书阅读与推荐(续二)后端好书阅读与推荐(续三)后端好书阅读与推荐(续四)后端好书阅读与推荐(续五)后端好书阅读与推荐(续六) Elasticsearch权威指南 El...

    z2xy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<