2017-02-24 96 views
1

假設我已經打開開發/民意調查作爲mDevPoll,是安全對我來說,同時從多個線程調用這樣的代碼是否可以同時從多個線程調用write()安全?

struct pollfd tmp_pfd; 
tmp_pfd.fd = fd; 
tmp_pfd.events = POLLIN; 

// Write pollfd to /dev/poll 
write(mDevPoll, &tmp_pfd, sizeof(struct pollfd)); 

...,或者我需要添加自己的同步原語周圍mDevPoll

+0

我很肯定你需要做你自己的同步。對於使用多線程應用程序的I/O,我的經驗是I/O不是線程安全的。 http://stackoverflow.com/questions/19974548/are-functions-in-the-c-standard-library-thread-safe有關於線程安全和C標準庫的一些討論,儘管它沒有討論'write()'特別是。然後有這篇文章,write(),線程安全和POSIX https://lwn.net/Articles/180387/ –

+2

'write()'是線程安全的。但是,是否將從多個線程到同一個文件描述符的'write()'併發原子化是另一個問題。假設寫入文件不是太大,用'pwrite()'來分隔文件的大部分部分,'writev()'是可以的。 – EOF

+3

'write()'是POSIX的函數列表,如果「兩個線程分別調用其中一個函數,每個調用將看到另一個調用的所有指定的效果,或者它們都不是。 http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_07 –

回答

5

Solaris 10聲稱符合POSIX標準。 write()函數不在the handful of system interfaces that POSIX permits to be non-thread-safe之間,所以我們可以得出結論:在Solaris 10上,從兩個或更多線程同時調用write()是安全的。

POSIX在這些functions whose effects are atomic relative to each other之間運行常規文件或符號鏈接時也指定write()。具體來說,它說,

If two threads each call one of these functions, each call shall either see all of the specified effects of the other call, or none of them.

如果你寫被定向到一個普通的文件,那麼這將足以得出結論,你提出的多線程操作是安全的,在這個意義上,他們不會互相干擾,並且在一次調用中寫入的數據不會與任何線程中不同調用寫入的數據混雜在一起。不幸的是,/dev/poll不是一個普通的文件,所以不適用於你。

您還應該知道,write()通常不需要傳輸單個調用中指定的全部字節數。對於一般用途,因此必須準備通過使用循環在多個調用中傳送所需的字節。 Solaris可能提供超出POSIX表達的適用保證,可能是特定於目標設備的保證,但如果沒有這樣的保證,可以想象其中一個線程執行部分寫入,而下一個寫入由另一個線程執行。這很可能不會產生你想要或預期的結果。

+1

我會在我的回答和註釋中添加我注意到的:在這種情況下('write()'到'/ dev/poll'),幾乎可以肯定*是安全的。而且最好是安全的,因爲我的一些代碼實現了OP所需的功能,並且十年前我已經提供了該代碼 - 從那以後它一直工作正常。不幸的是,我不記得,也沒有記錄爲什麼我不鎖定'write()'。但我打算今天晚些時候通過Illumos源代碼來確定。 –

+0

Ouch。我不再那麼確定 - 快速查看我的Solaris 11盒子上的'man poll.7d'顯示了一個例子,在部分寫入'/ dev/poll'時出錯。現在我知道*我將不得不檢查已發佈的源代碼。 –

+0

安德魯,我會很感激您通過源代碼看到的更新。儘管如此,由於我不能假定我的write()會在一次調用中完成,所以我將堅持鎖定write()調用。謝謝。 – Wad

4

即使write()是完全線程安全的(禁止執行錯誤...),理論上這並不安全。 (強調我的): 。

The write() function shall attempt to writenbyte bytes from the buffer pointed to by buf to the file associated with the open file descriptor, fildes .

...

RETURN VALUE

Upon successful completion, these functions shall return the number of bytes actually written ...

誰也不能保證,你不會得到部分write(),所以即使每個人write()調用是原子,它不一定完整,所以你仍然可以得到交錯的數據,因爲它可能需要更多比撥打write()要完全寫入所有數據。在實踐中,如果你只做相對較小的調用write(),你可能永遠不會看到局部的write(),「小」和「可能」是取決於你的實現的不確定值。

我經常發表使用與O_APPEND打開普通文件解鎖單write()調用,以提高記錄的性能的代碼 - 建立一個日誌條目,然後write()一次調用整個條目。我已經從來沒有在Linux和Solaris系統上做了幾乎幾十年的部分或交錯的write()結果,即使許多進程寫入相同的日誌文件。但是再次,它是一個文本日誌文件,如果發生部分或交錯的write(),則不會造成真正的損壞甚至數據丟失。

但是,在這種情況下,您正在將少量字節「寫入」內核結構。您可以在Illumos.org上查看Solaris /dev/poll內核驅動程序源代碼,並查看部分write()的可能性。我懷疑這實際上是不可能的 - 因爲我回去看了十年前爲我公司的軟件庫編寫的多平臺調查班。在Solaris上,它使用/dev/poll和解鎖來自多個線程的write()調用。它已經工作正常了十年......

的Solaris的/ dev /泳池設備驅動程序源代碼分析

的(開放)的Solaris源代碼可以在這裏找到:http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/io/devpoll.c#628

dpwrite()函數是實際執行「寫入」操作的/dev/poll驅動程序中的代碼。我使用引號是因爲它根本不是一個寫操作 - 數據不會像內核中的數據那樣傳輸,而是代表被輪詢的文件描述符集被更新。

將數據從用戶空間複製到內核空間 - 到使用kmem_alloc()獲取的內存緩衝區。我沒有看到任何可能的方式,可以是部分副本。分配成功或者不成功。在執行任何操作之前,代碼可能會被中斷,因爲它等待對內核結構的獨佔訪問權限write()

之後,最後返回調用是在結束 - 如果沒有錯誤,則整個呼叫被標記爲成功或任何錯誤的整個調用失敗:

995  if (error == 0) { 
996  /* 
997  * The state of uio_resid is updated only after the pollcache 
998  * is successfully modified. 
999  */ 
1000  uioskip(uiop, copysize); 
1001 } 
1002 return (error); 
1003} 

如果你挖通的Solaris內核代碼,你會看到uio_resid是成功調用後返回值爲write()的結果。

因此,這個呼叫肯定看起來是全有或全無。儘管在傳入多個描述符成功處理較早的描述符後,代碼似乎可以在文件描述符上返回錯誤,但代碼似乎沒有返回任何部分成功指示。

如果您一次只處理一個文件描述符,我會說write()操作是完全線程安全的,並且它幾乎可以確保線程安全,可以將多個文件描述符「寫入」更新爲駕駛員沒有明顯的方式返回部分write()的結果。

+1

這並不意味着寫入不是線程安全的! write在標準中明確指定爲線程安全的(*如果兩個線程分別調用其中一個函數,則每個調用都將看到另一個調用的所有指定效果,或者它們都不*,這意味着它是線程安全的* *他們的效果沒有重疊**)。寫可能不會寫所有要求,因爲有很多的條件可能會導致部分失敗(沒有足夠的空間等)。 –

+1

@ Jean-BaptisteYunès哦,絕對。 'write()'當然是線程安全的。問題是部分'write()'可能需要多次調用才能完全寫入數據,而當它是「線程安全」時,可能會導致交錯數據。在實踐中,這取決於您願意承擔多大的風險,以便從簡單且易於維護的代碼中獲得稍好的性能。我會補充一些說明。 –

+1

但在這種情況下,這不是一個單一的寫!單一寫入寫入與其他「併發」寫入是一致的,這就是要點。確保最大程度的故障安全語義將需要某種昂貴的事務語義。 –

相關問題