私はマルチスレッド アプリケーションを持っています。これはstd::cout
、ロックなしでログを大量に使用します。このような場合、ロック機構を簡単に追加してstd::cout
スレッドセーフにする方法を教えてください。
std::cout
発生するたびに検索して、ロックコードの行を追加したくありません。それは面倒すぎる。
より良い練習はありますか?
私はマルチスレッド アプリケーションを持っています。これはstd::cout
、ロックなしでログを大量に使用します。このような場合、ロック機構を簡単に追加してstd::cout
スレッドセーフにする方法を教えてください。
std::cout
発生するたびに検索して、ロックコードの行を追加したくありません。それは面倒すぎる。
より良い練習はありますか?
これがすべてのコンパイラ/標準ライブラリのバージョンに当てはまるかどうかはわかりませんが、使用しているコードベースstd::cout::operator<<()
ではすでにスレッドセーフです。
複数のスレッドにわたって、文字列ごとに複数回連結するときに、実際にやろうとしていることがstd::cout
文字列の混合を停止すると仮定しています。operator<<
文字列が文字化けする理由は、これに「外部」レースがあるためoperator<<
です。このようなことが起こる可能性があります。
//Thread 1
std::cout << "the quick brown fox " << "jumped over the lazy dog " << std::endl;
//Thread 2
std::cout << "my mother washes" << " seashells by the sea shore" << std::endl;
//Could just as easily print like this or any other crazy order.
my mother washes the quick brown fox seashells by the sea shore \n
jumped over the lazy dog \n
その場合、独自のスレッド セーフな cout を作成したり、cout で使用するロックを実装したりするよりも、はるかに簡単な答えがあります。
cout に渡す前に文字列を作成するだけです
例えば。
//There are other ways, but stringstream uses << just like cout..
std::stringstream msg;
msg << "Error:" << Err_num << ", " << ErrorString( Err_num ) << "\n";
std::cout << msg.str();
この方法では、文字列はすでに完全に形成されているため文字化けすることはありません。さらに、文字列をディスパッチする前に文字列を完全に形成することをお勧めします。
注:この回答はC ++ 20より前のものであるため、個別のバッファリングでは使用されませんstd::osyncstream
が、代わりにロックが使用されます。
cout
ミューテックスをラップして関連付ける独自のクラスを実装できると思います。そのoperator <<
新しいクラスのは、次の3つのことを行います。
<<
、ラップされたストリームと渡された引数の演算子を実行しますこの異なるクラスは、ロックを保持<<
し、ラップされたストリームにオペレーターを委任します。その2番目のクラスのデストラクタは、最終的にロックを破棄し、ミューテックスを解放します。
したがって、単一のステートメントとして、つまり単一の<<
呼び出しシーケンスとして記述した出力は、すべての出力が同じミューテックスでそのオブジェクトを通過する限り、アトミックに出力されます。
synchronized_ostream
2つのクラスをとと呼びましょうlocked_ostream
。がラップsync_cout
するインスタンスの場合、シーケンスsynchronized_ostream
std::cout
sync_cout << "Hello, " << name << "!" << std::endl;
次のアクションが発生します。
synchronized_ostream::operator<<
ロックを取得しますsynchronized_ostream::operator<<
「こんにちは」の印刷をに委任しますcout
operator<<(std::ostream&, const char*)
「こんにちは」と表示されますsynchronized_ostream::operator<<
を構築し、locked_ostream
それにロックを渡しますlocked_ostream::operator<<
の印刷を委任しname
ますcout
operator<<(std::ostream&, std::string)
名前を印刷しますcout
感嘆符とエンドラインマニピュレータで同じ委任が発生しますlocked_ostream
、ロックが解除されます一時オブジェクトを作成し、デストラクタに保護コードを配置するというこの質問で与えられた Nicolás のトリックが本当に気に入っています。
/** Thread safe cout class
* Exemple of use:
* PrintThread{} << "Hello world!" << std::endl;
*/
class PrintThread: public std::ostringstream
{
public:
PrintThread() = default;
~PrintThread()
{
std::lock_guard<std::mutex> guard(_mutexPrint);
std::cout << this->str();
}
private:
static std::mutex _mutexPrint;
};
std::mutex PrintThread::_mutexPrint{};
std::cout
その後、任意のスレッドから通常の として使用できます。
PrintThread{} << "my_val=" << val << std::endl;
オブジェクトは定期的にデータを収集しますostringstream
。昏睡状態に達するとすぐに、オブジェクトは破壊され、収集されたすべての情報がフラッシュされます。
C++11 アプリケーションを高速にデバッグし、インターリーブ出力を回避するために、次のような小さな関数を記述します。
...
#include <mutex>
...
mutex m_screen;
...
void msg(char const * const message);
...
void msg(char const * const message)
{
m_screen.lock();
cout << message << endl;
m_screen.unlock();
}
これらのタイプの関数を出力に使用します。数値が必要な場合は、次のようなものを使用します。
void msgInt(char const * const message, int const &value);
...
void msgInt(char const * const message, int const &value)
{
m_screen.lock();
cout << message << " = " << value << endl;
m_screen.unlock();
}
これは簡単で、うまく機能しますが、技術的に正しいかどうかはわかりません。ですので、ご意見をお聞かせいただければ幸いです。
さて、私はこれを読んでいませんでした:
std::cout が出現するたびに検索して、ロックコードの行を追加したくありません。
ごめんなさい。しかし、それが誰かに役立つことを願っています。
A feasible solution uses a line-buffer for each thread. You might get interleaved lines, but not interleaved characters. If you attach that to thread-local storage, you also avoid lock contention issues. Then, when a line is full (or on flush, if you want), you write it to stdout. This last operation of course has to use a lock. You stuff all this into a streambuffer, which you put between std::cout and it's original streambuffer.
The problem this doesn't solve is things like format flags (e.g. hex/dec/oct for numbers), which can sometimes percolate between threads, because they are attached to the stream. It's nothing bad, assuming you're only logging and not using it for important data. It helps to just not format things specially. If you need hex output for certain numbers, try this:
template<typename integer_type>
std::string hex(integer_type v)
{
/* Notes:
1. using showbase would still not show the 0x for a zero
2. using (v + 0) converts an unsigned char to a type
that is recognized as integer instead of as character */
std::stringstream s;
s << "0x" << std::setfill('0') << std::hex
<< std::setw(2 * sizeof v) << (v + 0);
return s.str();
}
Similar approaches work for other formats as well.
私はあなたと同様の問題を抱えていました。次のクラスを使用できます。これは への出力のみをサポートしstd::cout
ますが、一般的なものが必要な場合はお知らせください。以下のコードでtsprint
は、クラスのインライン一時オブジェクトを作成しますThreadSafePrinter
。の代わりにを使用している場合は、必要tsprint
に応じてに変更できます。そのため、 のインスタンスを置き換える必要はありませんが、一般的にはこのような方法はお勧めしません。いずれにしても、プロジェクトの最初からこのようなデバッグ行に特別な出力シンボルを使用することをお勧めします。cout
cout
std::cout
cout
私もこの解決策が好きです:1。私のソリューションでは、すべてのスレッドが対応する静的オブジェクトへの挿入を続行thread_local
stringstream
し、デストラクタでトリガーされるフラッシュが必要な場合にのみミューテックスをロックできます。これにより、ミューテックスロックが保持される期間が短縮され、効率が向上することが期待されます。1sync_endl
で述べた解決策と同様のメカニズムを含めることができるかもしれません。
class ThreadSafePrinter
{
static mutex m;
static thread_local stringstream ss;
public:
ThreadSafePrinter() = default;
~ThreadSafePrinter()
{
lock_guard lg(m);
std::cout << ss.str();
ss.clear();
}
template<typename T>
ThreadSafePrinter& operator << (const T& c)
{
ss << c;
return *this;
}
// this is the type of std::cout
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
// this is the function signature of std::endl
typedef CoutType& (*StandardEndLine)(CoutType&);
// define an operator<< to take in std::endl
ThreadSafePrinter& operator<<(StandardEndLine manip)
{
manip(ss);
return *this;
}
};
mutex ThreadSafePrinter::m;
thread_local stringstream ThreadSafePrinter::ss;
#define tsprint ThreadSafePrinter()
void main()
{
tsprint << "asd ";
tsprint << "dfg";
}