多線程、多進程寫同一日誌情況下的日誌庫中 I/O 的選型

文件描述符與 inode 相關背景知識


出自《The Linux Programming Interface》


多線程

有上面的背景知識可知,多線程情況下寫同一文件用的是同一個【文件偏移量】,因此只要單條寫日誌操作是原子操作,就不會出現日誌混亂的情況。

系統 I/O

系統 I/O write() 不帶應用層緩衝(進程級別緩衝),因此只要保證單條日誌操作之調用一次 write() 就可以保證多線程是安全的。


標準 I/O

As an example, the POSIX standard requires that C stdio FILE* operations are atomic.(https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html)

待確認:多個線程是否用同一緩衝區?

先假設是(個人認爲是,緩衝區應該是全局變量),全進程共享緩衝區,只要單條寫日誌操作是原子操作,也不會出現日誌混亂。


多進程

標準 I/O

如果用標準 I/O, 不管單條寫日誌操作是不是原子操作,都會出現日誌混亂,因爲哪個進程先沖刷緩衝區不確定,更不用說【文件偏移量】不同步的問題了。如果沒寫一條日誌就沖刷一次,又會大大降低性能。


系統 I/O

系統 I/O 的緩衝機制是內核級別的,因此只要保證一下兩點,就不會日誌混亂:

1. 單條寫日誌操作是原子操作;

2. 【設置偏移量+write】 是原子操作。


如何做到【設置偏移量+write】 是原子操作呢?

由上圖可知,如果 fd 是從父進程繼承過來的,【文件偏移量】是共享的,不用人工干預;

如果進程是毫不關聯的或者是分別打開的,那麼如何解決【文件偏移量】的問題呢?假設每次 【lseek+write】,這種操作不是原子操作,如何保證【設置偏移量+write】 是原子操作呢?open() 提供了一個 O_APPEND 的 FLAG, 每次直接 write() 都是追加到文件末尾,這樣【設置偏移量+write】就是一步完成了。(The Linux Programming Interface)

注意:open() 時如果設置了  O_APPEND, write 前 lseek 是無效的。


更新文件偏移量的內核 bug

Among the APIs subsequently listed are write() and writev(2).  And among the effects that should be atomic across threads (and processes) are updates of the file offset.  However, on Linux before version 3.14, this was not the case: if two processes that share an open file description (see open(2)) perform a write() (or writev(2)) at the same time, then the I/O operations were not atomic with respect updating the file offset, with the result that the blocks of data output by the two processes might (incorrectly) overlap.  This problem was fixed in Linux 3.14.(http://man7.org/linux/man-pages/man2/write.2.html)

共享系統級文件描述符時,【更新偏移量+寫】不是原子操作,導致即使共享偏移量也會有問題,kernel 3.14 中才修復。


日誌滾動(rotate)——以上說的都是錯的

在實際應用中,日誌是需要滾動的(rotate),即超過配置的大小就要切換文件或者從頭開始寫,這樣單一的寫日誌操作原子性已經不能使用多線程或多進程的場景了,還是需要加鎖。

在日誌滾動場景中,以下幾步操作是必須的:

1. 檢查日誌大小是否超過配置值;

2. 切換日誌文件名或移動到文件首部。


此外,write(系統調用)、fwrite(標準I/O)並不保證一次寫完用戶所有數據(機率比較小,通過返回值也可以判斷是否寫完)。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章