寫文件操做探微

多進程讀寫同一個文件的問題

不考慮文件內容的錯亂,多進程是能夠同時讀寫一個文件的。當一個進程在寫,讀的進程可否讀到最新的內容,取決於最新的內容是否真正寫到了磁盤上。java

寫緩存與寫磁盤

先看下寫文件操做的流程結構圖:python

clipboard.png

磁盤緩存是物理內存的一部分,專門供操做系統用做讀寫磁盤的緩衝之用。磁盤緩存與「硬盤自帶的緩存」是不同的概念,它的大小是能夠動態設置的,而不像硬盤緩存在出廠的時候固定就是32M或64M。
咱們一般用到的寫文件API,實際上是寫到磁盤緩存上,可用python語言作一個實驗:linux

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字符串在文件關閉前只在磁盤緩存裏,還未真正寫到磁盤上,因此讀進程沒法讀出。redis

如何確保寫到磁盤上而不僅是磁盤緩存裏呢?python文檔給出了建議:windows

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\n')
        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的內容依然能夠安全到達磁盤上。

相關文章
相關標籤/搜索