Linuxでprintf
を使用してstdoutに書き込むのはスレッドセーフですか?下位レベルのwrite
コマンドの使用についてはどうですか?
C標準では指定されていません。C標準ライブラリの実装によって異なります。実際、特定のシステム(組み込みシステムなど)にはマルチスレッド機能がないため、C標準ではスレッドについてはまったく触れられていません。
GNU実装(glibc
)では、FILE*
オブジェクトを処理するstdioの高レベル関数のほとんどがスレッドセーフです。通常、名前にはunlocked
が含まれます(たとえばgetc_unlocked(3)
)。ただし、スレッドの安全性は関数ごとの呼び出しレベルです。printf(3)
を複数回呼び出すと、たとえば、これらの各呼び出しはアトミックに出力されることが保証されていますが、printf()
への呼び出しの間に他のスレッドが出力を出力する場合があります。I/ O呼び出しのシーケンスがアトミックに出力されるようにする場合は、これらの関数はFILE
ハンドルをロックするためにflockfile(3)/funlockfile(3)
のペアで呼び出します。これらの関数は再入可能であるため、それらの間でprintf()
を安全に呼び出すことができます。 printf()
自体がflockfile()
を呼び出しても、デッドロックが発生します。
write(2)
などの低レベルのI/O呼び出しはスレッドセーフである必要がありますが、100%確実ではありません-write()
はカーネルにシステムコールを実行してI /を実行しますO.これがどのように発生するかは、使用しているカーネルによって異なります。古いシステムでは、sysenter
命令、またはint
(割り込み)命令の可能性があります。カーネルに入ったら、I/Oがスレッドセーフであることを確認するのはカーネル次第です。 Darwin Kernelバージョン8.11.1で行ったテストでは、write(2)
はスレッドセーフであるように見えます。
「スレッドセーフ」と呼ぶかどうかは、スレッドセーフの定義に依存します。 POSIXはロックを使用するためにstdio
関数を必要とするため、複数のスレッドからFILE
を同時に使用しても、プログラムがクラッシュしたり、printf
オブジェクトの状態が破損したりすることはありません。 ただし、すべてのstdio
操作は、fgetc
およびfputc
の繰り返し呼び出しに関して正式に指定されているため、大規模な原子性は保証されません。つまり、スレッド1と2が"Hello\n"
および"Goodbye\n"
同時に、出力がどちらかになる保証はありません"Hello\nGoodbye\n"
または"Goodbye\nHello\n"
。 "HGelolodboy\ne\n"
。実際には、ほとんどの実装は、より効率的であるという理由だけで、より高いレベルの書き込み呼び出し全体に対して単一のロックを取得しますが、プログラムはそうするべきではありません。これが行われていない場合があります。たとえば、実装ではおそらくバッファリングされていないストリームのロックを完全に省略できます。
編集:原子性に関する上記のテキストは正しくありません。 POSIXはすべてのstdio
操作がアトミックであることを保証しますが、その保証はflockfile
のドキュメントに隠されています: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile .html
(FILE *)オブジェクトを参照するすべての関数は、内部でflockfile()およびfunlockfile()を使用してこれらの(FILE *)オブジェクトの所有権を取得するかのように動作します。
flockfile
、ftrylockfile
、およびfunlockfile
関数を自分で使用して、単一関数呼び出しよりも大きいアトミック書き込みを実現できます。
これらはどちらも、複数のスレッドが同じファイル記述子で呼び出してもアプリケーションがクラッシュしない程度までスレッドセーフです。ただし、アプリケーションレベルのロックがなければ、書き込まれたものはすべてインターリーブされる可能性があります。
この質問が出された(そして最後に回答された)ため、Cは新しい標準を取得しました。
C11はマルチスレッドサポートを備え、ストリームのマルチスレッド動作に対処します。
§7.21.2ストリーム
¶7各ストリームには、データの競合を防ぐために使用される関連付けられたロックがあります複数の実行スレッドがストリームにアクセスする場合、にインターリービングを制限する複数のスレッドによって実行されるストリーム操作。一度に1つのスレッドのみがこのロックを保持できます。ロックは再入可能です。単一のスレッドが、一度に複数回ロックを保持する場合があります。
¶8ストリームの位置を読み取り、書き込み、配置、またはクエリするすべての関数は、アクセスする前にストリームをロックします。アクセスが完了すると、ストリームに関連付けられたロックを解放します。
したがって、C11スレッドの実装では、printf
の使用がスレッドセーフであることを保証する必要があります。
原子性かどうか(インターリーブなしの場合と同様)1)は保証されていますが、restrictingインターリーブの標準スポークはpreventing、それはデータ競争に義務付けられました。
私はそれが保証されていることに傾いています。標準では、restrictingインターリーブについて説明しています。これは、結果を変更しないインターリーブが引き続き発生するためです。 egfwrite
数バイト、fseek
元のオフセットまでさらにfwrite
を返し、両方のfwrite
sは連続しています。実装では、これらの2つのfwrite
sを自由に並べ替えて、1つの書き込みにマージできます。
1 :例については、 R .. 's answer の取り消し線のテキストを参照してください。
スレッドセーフです。 printfは再入可能である必要があり、プログラムの異常や破損を引き起こすことはありません。
あるスレッドからの出力が別のスレッドからの出力の途中で開始されないことは保証できません。複数のアクセスを防ぐために独自のロックされた出力コードを開発する必要があることを気にする場合。