c++multithreadinglogginglockingiostream

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


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?


Solution

  • Note: This answer is pre-C++20 so it does not use std::osyncstream with its separate buffering, but uses a lock instead.

    I guess you could implement your own class which wraps cout and associates a mutex with it. The operator << of that new class would do three things:

    1. create a lock for the mutex, possibly blocking other threads
    2. do the output, i.e. do the operator << for the wrapped stream and the passed argument
    3. construct an instance of a different class, passing the lock to that

    This different class would keep the lock and delegate operator << to the wrapped stream. The destructor of that second class would eventually destroy the lock and release the mutex.

    So any output you write as a single statement, i.e. as a single sequence of << invocations, will be printed atomically as long as all your output goes through that object with the same mutex.

    Let's call the two classes synchronized_ostream and locked_ostream. If sync_cout is an instance of synchronized_ostream which wraps std::cout, then the sequence

    sync_cout << "Hello, " << name << "!" << std::endl;
    

    would result in the following actions:

    1. synchronized_ostream::operator<< would aquire the lock
    2. synchronized_ostream::operator<< would delegate the printing of "Hello, " to cout
    3. operator<<(std::ostream&, const char*) would print "Hello, "
    4. synchronized_ostream::operator<< would construct a locked_ostream and pass the lock to that
    5. locked_ostream::operator<< would delegate the printing of name to cout
    6. operator<<(std::ostream&, std::string) would print the name
    7. The same delegation to cout happens for the exclamation point and the endline manipulator
    8. The locked_ostream temporary gets destructed, the lock is released