標籤:

多線程追加文件,不加鎖,會出現什麼情況 ?

線程1追加111111111,線程2追加22222222222,一直到線程9追加9999999999,

最後生成的內容會出現,11347893332211這種情況嗎,


無定義。

順便用這個問題來回答一下這個問題(linux/sem.h和sys/sem.h有什麼區別? - in nek 的回答)下有人問我的問題:什麼叫基於語義來進行編程?

其實這句話完整說應該是:要基於「自然語義」來進行編程。

我是軟體架構師,我考慮的問題大部分時候是讓自己的軟體活得更久。軟體面對各種各樣的變化,軟體換場景了,硬體升級了,操作系統升級了,換操作系統類型了,換開發庫了,換資料庫了……不一而足,怎麼樣讓軟體活下來?我的方法是,「基於語義進行編程」。

語義是一句話的自然的,表面的意思。大部分軟體,都是解決人的問題,所以它的介面發展受人的思維所控制,這種介面上的控制,進一步會控制到內部的數據流,脫離人的思維去構造人腦邏輯之外的計算機邏輯,會增加介面發展的風險,會很快造成構架的破壞。

另一方面說,現在的計算機編程,通常都是要採用敏捷迭代的方式完成的。所以,一個定義(變數,函數)的含義,是這個定義的名稱(以及其自然語義)決定的,而不是這個定義當前如何使用決定的。我的log函數現在實現成了printf,不表示你可以用printf來代替log,因為這個函數未來可以變更為寫入資料庫。

推廣開去,比如現實中,一個網卡有一個mac,有兩個mac的叫聚合,在OS的自然定義中,人們認為兩個mac聚合是兩張網卡的聚合。但你非要作死,在定義網卡的時候,非要用一個數組來放多個mac,後面配合的時候,網卡子系統不修理你,外部互聯的子系統也會修理你,因為沒有人會按你這樣的考慮來構造其他系統,你就死得很快。

你要寫日誌,就用log()函數,你要列印輸出,就用printf函數,你不能用printf來寫日誌,我知道你也能跑,但printf的語義不是日誌,到你真要列印的時候,這兩個函數會給你帶來麻煩,你更不要用assert()等來當作日誌輸出(臨時程序我不管,我說的是嚴肅的程序),這就叫基於語義編程,而不是基於邏輯(能跑)來編程。

有人問我malloc(0)返回什麼,我說你去查手冊,他說,他不用查,他試過了,是一個非0的指針,我說So what?你在幾個平台上驗證過?你看看man,人家是這樣說的:

If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free()

你要基於這個來寫程序,這樣的程序才能活得長遠。

理解這一點,也許更容易理解我前面的「無定義」是什麼意思了。


題主的意思是想要避免寫出的內容產生交錯,要做到這種效果的最好方法是使用O_APPEND的write,不僅僅適用於進程內的多線程,也適用於多進程,nginx的日誌也是使用了這個技巧來避免日誌內容產生交錯。

O_APPEND標識的使用可以讓你的write把當前指針移到文件末尾,然後寫出內容。並且整個過程是作為原子操作,不會產生交錯現象。這個原子性是有操作系統保證的,只要文件的使用者都是以O_APPEND方式,那麼不管多進程之間的文件描述符是繼承的還是單獨打開的,都能夠達到原子的效果。


會或者不會,取決於如何追加。

最簡單的例子就是向標準輸出(這個文件)寫入線程特異性的內容。


同一個進程內, 針對同一個FILE*操作(比如fwrite), 是線程安全的. 當然這隻在POSIX兼容的系統上成立, Windows上的FILE*的操作並不是線程安全的.

http://gcc.gnu.org/onlinedocs/libstdc++/manual/using_concurrency.html

As an example, the POSIX standard requires that C stdio FILE* operations are atomic. POSIX-conforming C libraries (e.g, on Solaris and GNU/Linux) have an internal mutex to serialize operations on FILE*s. However, you still need to not do stupid things like calling fclose(fs) in one thread followed by an access of fs in another.

PS:

我就在Linux上使用這一特性來記錄log.


想想也知道當然可以互相插隊啊,而且根本不用測試來證明。如果不行的話,支持Streaming的CGI Web Server都寫不出來


linux下write操作,使用O_APPEND選項給文件追加數據,如果write大小不超過PIPE_BUF的話保證是原子操作,大小超過PIPE_BUF不保證原子性。

http://linux.die.net/man/3/write

=====

上述限制雖僅適用於管道IO,但linux下程序IO對管道的使用常常是隱式的(fifo/重定向/NFS等),遵循上述限制是安全的做法。


符合POSIX標準的基於FILE*的操作是reentrant

The POSIX.1 and C-language functions that operate on character streams (represented by pointers to objects of type FILE) are required by POSIX.1c to be implemented in such a way that reentrancy is achieved (see ISO/IEC 9945:1-1996, §8.2).

Thread-safety and POSIX.1


如果是linux下用fwrite,最簡單的辦法就是去看一下fwrite源碼,你會發現裡面用pthreadmutex做了保護,所以如果是同一個FILE*是沒有問題的。


同意dong的答案,但是這裡面有些例外,比如windows是不保證的,因為他不符合posix的標準,文件系統如果是NFS等非本地文件系統的可能不能保證


會出現的,測試過


推薦閱讀:

linux系統會中比特幣病毒嗎?
有沒有學習Linux比較好的入門書籍?
如何紮實系統地學好後端開發(Linux 環境下)?細分方向有哪些?可否推薦一些好的開源項目?
為什麼操作系統必須要有內核?
基於什麼樣的理由或特徵可以判別某個系統是 Android 的修改版本而不是另一個基於 Linux 開發的系統?

TAG:Linux |