How to Easily Make Std::Cout Thread-Safe

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

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

Is cout synchronized/thread-safe?

The C++03 standard does not say anything about it. When you have no guarantees about the thread-safety of something, you should treat it as not thread-safe.

Of particular interest here is the fact that cout is buffered. Even if the calls to write (or whatever it is that accomplishes that effect in that particular implementation) are guaranteed to be mutually exclusive, the buffer might be shared by the different threads. This will quickly lead to corruption of the internal state of the stream.

And even if access to the buffer is guaranteed to be thread-safe, what do you think will happen in this code?

// in one thread
cout << "The operation took " << result << " seconds.";

// in another thread
cout << "Hello world! Hello " << name << "!";

You probably want each line here to act in mutual exclusion. But how can an implementation guarantee that?

In C++11, we do have some guarantees. The FDIS says the following in §27.4.1 [iostream.objects.overview]:

Concurrent access to a synchronized (§27.5.3.4) standard iostream object’s formatted and unformatted input (§27.7.2.1) and output (§27.7.3.1) functions or a standard C stream by multiple threads shall not result
in a data race (§1.10). [ Note: Users must still synchronize concurrent use of these objects and streams by
multiple threads if they wish to avoid interleaved characters. — end note ]

So, you won't get corrupted streams, but you still need to synchronize them manually if you don't want the output to be garbage.

Thread safe std::cout

Your problem is here: std::lock_guard<std::mutex>{global_mtx}; creates a lock guard and immediately releases it. You need to create a variable to hold the lock, like std::lock_guard<std::mutex> lock{global_mtx};.

Is writing a single string to cout thread safe in C++98

As has been pointed out in the comments, C++98 doesn't offer any notion of threads in the standard, so you can't answer this question by reference to the standard.

However any reasonable C++98 implementation intended to be used with threads (i.e., most of them outside of some embedded markets) would almost certainly offer at least a more-or-less safe std::cout for "plain" use cases. After all, using that stream for output is very common, and using it across threads in a threaded program will also be very common.

Unfortunately, that's about as much as you can say with specificity. Note, in particular, that I'm not even defining "safe" in a particular way. At least you probably expect that it won't crash, corrupt your program or produce output "out of thin air". Beyond that, it depends.

What Does it Depend On

The next step is to check the implementation itself. Hopefully you have the source1!

Typically you'll find that the implementation has some thread-agnostic code (e.g,. copying into stack-local buffers) and at some point locks and then manipulates shared state inside the std::cout object. When and where it locks is important.

Single String

For example, one would hope that the output of a single item on two threads, like std::cout << "AAAA" on thread 1 and std::cout << "BBBB" on thread 2 would at least result in "not-interleaved" output of AAAABBBB or BBBBAAAA but never BBAAAABB or something like that. This may not be true for implementations that buffer in certain ways! For example, the implementation may lock, then copy as much into an internal buffer as possible and if full, output, and then unlock and proceed again.

Keep in mind that even the std::cout object is totally locked (i.e., essentially the entire operator<<(const char *) is run under a lock), you might see interleaving of even single string output when it is run concurrently with other code that is writing to stdout but through a mechanism other than std::cout - such as printf().

In this case the C/C++ runtimes generally won't share any lock, and you'll be relying on the locking of the underlying write()-type call offered by the OS. Although this call is fully locked and thread-safe, it might not write the entire string in one shot (this is common, for example, when an intermediate buffer fills up such as when writing to a pipe).

Multiple Operators

The main problem with the stream interface is multiple operators. Something like std::cout << "AAA" << "BBB" ... is almost always turned into multiple calls on the shared std::cout object. Without external locking on your part, these are free to be interleaved in any way with other threads. Critically, this pretty much means the stream manipulators in iomanip are impossible to use safely. Something like std::cout << std::hex << 123 << std::dec will modify the stream globally, and some other thread that doesn't want hex output might run at the wrong time and get it anyways. Furthermore, the fact that the stream formatting state and flags can change in the middle of an operation may produce some weird, wonderful or downright terrible results.


1 Certainly the source is available for open source runtimes like libstc++ (used by gcc) and libc++ (used by default by LLVM on most non-Linux platforms). Microsoft appears to be providing the source for their C runtime as well.

using std::cout in multiple threads

The threads are using different mutex instances as the mutex is a local variable in the exec() function so locking the mutex is pointless as each thread will be locking its own mutex resulting in no synchronization between the threads. The same mutex instance must be used by the threads to achieve synchronization.

To correct in the posted code, make the mutex a member variable. However, if another Printer object was created then there would be no synchronization between threads that used different Printer instances. In this case, the mutex would need to be a static member variable to ensure synchronization:

class Printer
{
public:
//...
private:
static std::mutex mtx_;
};

std::mutex Printer::mtx_;

To ensure a mutex is always released, regardless if a function exits normally or via an exception, use std:lock_guard:

std::lock_guard<std::mutex> lock(m); // 'm' locked, and will be
// unlocked when 'lock' is destroyed.
std::cout<< "Hello " << std::this_thread::get_id() << std::endl;
std::chrono::milliseconds duration( 100 );
std::this_thread::sleep_for( duration );

Thread safety of std::cout insertion operator

I got the answer from Jonathan Wakely. Makes me feel rather stupid.

The difference is that on Fedora, libstdc++.so contains an explicit instantiation of the iostream classes. libstdc++.so isn't instrumented for ThreadSanitizer so it cannot detect any hazards related to it.



Related Topics



Leave a reply



Submit