121

In general I assume that streams are not synchronized, it is up to the user to do appropriate locking. However, do things like cout get special treatment in the standard library?

That is, if multiple threads are writing to cout can they corrupt the cout object? I understand that even if synchronized you'd still get randomly interleaved output, but is that interleaving guaranteed. That is, is it safe to use cout from multiple threads?

Is this vendor dependent? What does gcc do?


Important: Please provide some kind of reference for your answer if you say "yes" since I need some kind of proof of this.

My concern is also not about the underlying system calls, those are fine, but the streams add a layer of buffering on top.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
edA-qa mort-ora-y
  • 30,295
  • 39
  • 137
  • 267
  • 2
    This is vendor dependant. C++ (before C++0x) has no notion of multiple threads. – Sven Jun 16 '11 at 15:22
  • 2
    What about c++0x? It defines a memory model and what a thread is, so perhaps these things dripped through in the output? – rubenvb Jun 16 '11 at 15:30
  • 2
    Are there any vendors that makes it thread-safe? – edA-qa mort-ora-y Jun 16 '11 at 15:32
  • Anybody have a link to the most recent C++2011 proposed standard? – edA-qa mort-ora-y Jun 16 '11 at 17:36
  • @edA-qa mort-ora-y : The FDIS is no longer freely available, but the second-to-last draft can be found [here](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf). – ildjarn Jun 16 '11 at 18:00
  • @ildjarn: isn't n3290 more up to date than n3242? – Gene Bushuyev Jun 16 '11 at 19:35
  • @Gene : Yes, but as I said, it is no longer freely available (unless you're on the ISO committee presumably). [Try it and see](http://www.open-std.org/jtc1/sc22/wg21/prot/14882fdis/n3290.pdf). – ildjarn Jun 16 '11 at 19:36
  • @ildjarn: yes, just noticed that. I hope the pdfs I have won't expire. – Gene Bushuyev Jun 16 '11 at 19:38
  • Is there no way to get the current FDIS draft, even paying for it? I've tried the ISO site but they don't have it... – edA-qa mort-ora-y Jun 16 '11 at 20:00
  • @edA-qa mort-ora-y : Not that I'm aware of, but given that thousands of people got a copy of it before it was password protected and that you know the pdf's filename, I'm sure you can do a little creative googling and find a copy... – ildjarn Jun 16 '11 at 20:06
  • There's a problem with dueling standards here: C++ versus C versus POSIX. Neither C99 nor C++98 said a word about threads or thread safety. POSIX does have a lot to say about threads, and also about a lot of things that are peripheral to the threads library. A POSIX-compliant C compiler must provide a C library with thread-safe fprintf, fwrite, etc. (POSIX, so far, has nothing to say about C++, or the C++ library.) – David Hammen Jun 16 '11 at 20:10
  • 4
    In some sense this is where [`printf` shines](http://stackoverflow.com/q/26961023/183120) as the complete output is written to `stdout` in one shot; when using `std::cout` each link of the expression chain would be output separately to `stdout`; in between them there can be some other thread writing to `stdout` due to which the final output's order gets messed up. – legends2k Aug 18 '15 at 08:01

4 Answers4

113

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 2
    Technically true for C++98/C++03, but I think everybody knows that. But this does not answer the two interesting questions: What about C++0x? What do typical implementations actually _do_? – Nemo Jun 16 '11 at 15:46
  • Okay, so C++11 does clearly define that the standard stream objects, like `cout` will be synchronized (also sync'ed with the underlying stream, so sync'ed with the C API as well). – edA-qa mort-ora-y Jun 16 '11 at 16:59
  • 1
    @edA-qa mort-ora-y : No, you have that wrong. C++11 clearly defines that the standard stream objects _can_ be synchronized and retain well-defined behavior, not that they are by default. – ildjarn Jun 16 '11 at 18:02
  • 13
    @ildjarn - No, @edA-qa mort-ora-y is correct. As long as `cout.sync_with_stdio()` is true, using `cout` to output characters from multiple threads without additional synchronization is well-defined, but only on the level of individual bytes. Thus, `cout << "ab";` and `cout << "cd"` executed in different threads may output `acdb`, for example, but may not cause Undefined Behaviour. – JohannesD Jun 16 '11 at 18:31
  • 4
    @JohannesD : We're in agreement there -- it's synchronized with the underlying C API. My point is that it's not "synchronized" in a useful manner, i.e. one still needs manual synchronization if they don't want garbage data. – ildjarn Jun 16 '11 at 18:35
  • 2
    @ildjarn, I'm fine with the garbage data, that bit I understand. I'm just interested in the data race condition, which seems to be clear now. – edA-qa mort-ora-y Jun 16 '11 at 19:55
  • @edA-qa mort-ora-y : Fair enough :-] – ildjarn Jun 16 '11 at 20:07
  • Just wondering, is this answer up-to-date with C++14, 17 and 20? – Alex Nov 27 '20 at 01:58
16

This is a great question.

First, C++98/C++03 has no concept of "thread". So in that world, the question is meaningless.

What about C++0x? See Martinho's answer (which I admit surprised me).

How about specific implementations pre-C++0x? Well, for example, here is the source code for basic_streambuf<...>:sputc from GCC 4.5.2 ("streambuf" header):

 int_type
 sputc(char_type __c)
 {
   int_type __ret;
   if (__builtin_expect(this->pptr() < this->epptr(), true)) {
       *this->pptr() = __c;
        this->pbump(1);
        __ret = traits_type::to_int_type(__c);
      }
    else
        __ret = this->overflow(traits_type::to_int_type(__c));
    return __ret;
 }

Clearly, this performs no locking. And neither does xsputn. And this is definitely the type of streambuf that cout uses.

As far as I can tell, libstdc++ performs no locking around any of the stream operations. And I would not expect any, as that would be slow.

So with this implementation, obviously it is possible for two threads' output to corrupt each other (not just interleave).

Could this code corrupt the data structure itself? The answer depends on the possible interactions of these functions; e.g., what happens if one thread tries to flush the buffer while another tries to call xsputn or whatever. It might depend on how your compiler and CPU decide to reorder memory loads and stores; it would take a careful analysis to be sure. It also depends what your CPU does if two threads try to modify the same location concurrently.

In other words, even if it happens to work fine in your current environment, it might break when you update any of your runtime, compiler, or CPU.

Executive summary: "I wouldn't". Build a logging class that does proper locking, or move to C++0x.

As a weak alternative, you could set cout to unbuffered. It is likely (although not guaranteed) that would skip all logic related to the buffer and call write directly. Although that might be prohibitively slow.

Community
  • 1
  • 1
Nemo
  • 70,042
  • 10
  • 116
  • 153
8

The C++ Standard does not specify whether writing to streams is thread-safe, but usually it's not.

www.techrepublic.com/article/use-stl-streams-for-easy-c-plus-plus-thread-safe-logging

and also: Are standard output streams in C++ thread-safe (cout, cerr, clog)?

UPDATE

Please have a look at @Martinho Fernandes' answer to know about what the new standard C++11 tells about this.

Community
  • 1
  • 1
phoxis
  • 60,131
  • 14
  • 81
  • 117
6

As other answers mention, this is definitely vendor-specific since the C++ standard makes no mention of threading (this changes in C++0x).

GCC doesn't make a whole lot of promises about thread safety and I/O. But the documentation for what it does promise is here:

the key stuff is probably:

The __basic_file type is simply a collection of small wrappers around the C stdio layer (again, see the link under Structure). We do no locking ourselves, but simply pass through to calls to fopen, fwrite, and so forth.

So, for 3.0, the question of "is multithreading safe for I/O" must be answered with, "is your platform's C library threadsafe for I/O?" Some are by default, some are not; many offer multiple implementations of the C library with varying tradeoffs of threadsafety and efficiency. You, the programmer, are always required to take care with multiple threads.

(As an example, the POSIX standard requires that C stdio FILE* operations are atomic. POSIX-conforming C libraries (e.g, on Solaris and GNU/Linux) have an internal mutex to serialize operations on FILE*s. However, you still need to not do stupid things like calling fclose(fs) in one thread followed by an access of fs in another.)

So, if your platform's C library is threadsafe, then your fstream I/O operations will be threadsafe at the lowest level. For higher-level operations, such as manipulating the data contained in the stream formatting classes (e.g., setting up callbacks inside an std::ofstream), you need to guard such accesses like any other critical shared resource.

I don't know if anything has changed sine the 3.0 timeframe mentioned.

MSVC's thread safety documentation for iostreams can be found here: http://msdn.microsoft.com/en-us/library/c9ceah3b.aspx:

A single object is thread safe for reading from multiple threads. For example, given an object A, it is safe to read A from thread 1 and from thread 2 simultaneously.

If a single object is being written to by one thread, then all reads and writes to that object on the same or other threads must be protected. For example, given an object A, if thread 1 is writing to A, then thread 2 must be prevented from reading from or writing to A.

It is safe to read and write to one instance of a type even if another thread is reading or writing to a different instance of the same type. For example, given objects A and B of the same type, it is safe if A is being written in thread 1 and B is being read in thread 2.

...

iostream Classes

The iostream classes follow the same rules as the other classes, with one exception. It is safe to write to an object from multiple threads. For example, thread 1 can write to cout at the same time as thread 2. However, this can result in the output from the two threads being intermixed.

Note: Reading from a stream buffer is not considered to be a read operation. It should be considered as a write operation, because this changes the state of the class.

Note that that information is for the most recent version of MSVC (currently for VS 2010/MSVC 10/cl.exe 16.x). You can select the information for older versions of MSVC using a dropdown control on the page (and the information is different for older versions).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 1
    "I don't know if anything has changed sine the 3.0 timeframe mentioned." It definitely did. For the past several years, the g++ streams implementation has performed its own buffering. – Nemo Jun 16 '11 at 17:13