How to easily make std::cout thread-safe?

2019-01-21 23:02发布

I have a multi-threaded application, which heavily uses std::cout for logging without any locking. In such a case, how can I easily add lock mechanism to make std::cout thread-safe?

I don't want to search for each occurrence of std::cout and add a line of locking code. That is too tedious.

Any better practice?

9条回答
疯言疯语
2楼-- · 2019-01-21 23:57

I really like the trick from Nicolás given in this question of creating a temporary object and putting the protection code on the destructor.

/** 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{};

You can then use it as a regular std::cout, from any thread:

PrintThread{} << "my_val=" << val << std::endl;

The object collect data as a regular ostringstream. As soon the coma is reached, the object is destroyed and flush all collected information.

查看更多
Fickle 薄情
3楼-- · 2019-01-21 23:57

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<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) will 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.

查看更多
走好不送
4楼-- · 2019-01-22 00:00

While I can't be sure this applies to every compiler / version of std libs but in the code-base I'm using std::cout::operator<<() it is already thread-safe.

I'm assuming that what you're really trying to do it stop std::cout from mixing string when concatenating with the operator<< multiple time per string, across multiple threads.

The reason strings get garbled is because there is a "External" race on the operator<< this can lead to things like this happening.

//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 sure \n
jumped of the lazy dog \n

If that's the case there is a much simpler answer than making your own thread safe cout or implementing a lock to use with cout.

Simply compose your string before you pass it to cout

For example.

//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();

This way your stings can't be garbled because they are already fully formed, plus its also a better practice to fully form your strings anyway before dispatching them.

查看更多
登录 后发表回答