2017-02-24 5 views
1

は私がmDevPollとしてDEV /世論調査を開いたと仮定すると、それは私が複数のスレッドから同時に...この複数のスレッドから同時に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のリストの中にあります。「2つのスレッドがそれぞれこれらの関数の1つを呼び出すと、それぞれの呼び出しは、他の呼び出しの指定されたすべての効果を見るか、どちらも表示されません。 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では、一般的な意味では、2つ以上のスレッドから同時に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.

はその後、それがあなたの提案マルチスレッドのアクションは、彼らがお互いに干渉しないという意味で、安全であると結論するのに十分であると述べています1つのコールで書き込まれたデータは、どのスレッドでも別のコールによって書き込まれたデータと混同されません。残念ながら、/dev/pollは通常のファイルではないため、あなたには直接適用されません。

また、write()は、1回の呼び出しで指定された完全なバイト数を転送するのに一般的に必要ではないことにも注意してください。したがって、一般的な目的のために、ループを使用して、複数の呼び出しに渡って必要なバイトを転送する準備が必要です。 Solarisは、おそらく宛先デバイスに固有の、POSIXで表現されたものを超える適用可能な保証を提供するかもしれないが、そのような保証がなければ、あなたのスレッドの1つが部分的な書き込みを実行し、次の書き込みが別のスレッドによって実行されることが考えられる。それはあなたが望む、あるいは期待している結果を生み出すことはないでしょう。

+1

私の答えとそのコメントに書かれていることを追加します:この場合( 'write()'から '/ dev/poll'に)、ほぼ確実です*安全です。私のコードの中には、OPが望んでいるものを実装しているものがあり、10年前にそのコードを渡したので、安全であることがより安全です。残念ながら、私は 'write()'をロックしなかった理由を思い出さず、文書化しませんでした。しかし、私はIllumosのソースコードを今日後で読むことを計画しています。 –

+0

私はもはやそれほど確実ではありません。Solaris 11の 'man poll.7d'を見てみると、部分的に'/dev/poll'に書き込むとエラーが出るという例があります。今私は知っている*私は公開されたソースコードを調べなければならないだろう。 –

+0

Andrew、私はあなたがソースコードを調べるときに見つけたもので更新を感謝します。ところで、私は 'write()'が一回の呼び出しで処理されるとは仮定できないので、 'write()'呼び出しの周りでロックするつもりです。ありがとう。 – Wad

4

write()はスレッドセーフです(実装上のバグはありません...)。 Per the POSIX write() standard(強調鉱山): 。

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()への1回の呼び出しよりも時間がかかります。

write()コールが比較的小さい場合は、部分的にwrite()が表示されることはほとんどありません。「小さい」と「可能性があります」の値は実装によって異なります。ログエントリその後、write() 1回のコールでエントリ全体を構築する -

は私が日常のロギングのパフォーマンスを改善するためにO_APPENDで開いた定期的なファイルのロックを解除単一write()呼び出しを使用するコードを届けてきました。私はを一度も受け取りませんは、多くのプロセスが同じログファイルに書き込む場合でも、LinuxシステムとSolarisシステムでほぼ2〜30年にわたって部分的またはインタリーブされた結果です。しかしもう一度、それはテキストログファイルであり、部分的またはインターリーブされたwrite()が行われた場合、実際にダメージを与えたり、データを失ったりすることはありません。

しかし、この場合、カーネル構造にいくつかのバイトを「書いています」。あなたはIllumos.orgのSolaris /dev/pollカーネルドライバのソースコードを掘り下げ、write()部分の可能性を確認することができます。私はそれが事実上不可能だと思うだろう - 私はちょうど戻って、私が10年前に私の会社のソフトウェアライブラリのために書いたマルチプラットフォームのポールクラスを見た。 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を通して掘る場合カーネルコードを使用すると、成功した呼び出しの後にwrite()によって返される値になるのはuio_residです。

したがって、呼び出しは確かにすべてかどうかのようです。複数のディスクリプタが渡されたときに、以前のディスクリプタを正常に処理した後、コードがファイルディスクリプタにエラーを返す方法があるようですが、コードは部分的な成功指示を返すようには見えません。

一度に1つのファイル記述子しか処理していない場合、/dev/poll write()操作は完全にスレッドセーフで、複数のファイル記述子への更新を「書き込み」するのはスレッドセーフです。運転手が部分的な結果を返す明白な方法はありませんwrite()

+1

これは、書き込みがスレッドセーフではないことを意味しません! writeは、標準でスレッドセーフとして明示的に指定されています(* 2つのスレッドがそれぞれこれらの関数のいずれかを呼び出す場合、各呼び出しは指定された他の呼び出しのすべての効果を見るか、 *その効果の重複はない**)。部分的な失敗(十分なスペースがないなど)につながる可能性のある多くの条件があるため、もちろん、要求されたものすべてを書き込むことはできません。 –

+1

@ Jean-BaptisteYunèsああ、絶対に。 'write()'は確実にスレッドセーフです。問題は、部分的な 'write()'がデータを完全に書き込むために複数の呼び出しを必要とすることがあり、スレッドセーフであるにもかかわらずインターリーブされたデータになる可能性があるということです。実際には、シンプルで保守が容易なコードからわずかに優れたパフォーマンスを得るためには、どれだけのリスクを抱えているかによって異なります。私はいくつかの説明を追加します。 –

+1

しかし、この場合、これは1つの書き込みではありません! 1つの書き込みが書き込むものは、他の「並行」書き込みとは一貫しており、その点です。最大限のフェイルセーフセマンティクを保証するには、ある種の高価なトランザクションセマンティックが必要になります。 –

関連する問題