3

I know that stricly speaking in C++98 nothing is thread-safe, because in the standard there are no threads prior to C++11. However, in practice threads have been used in C++ long before C++11.

So lets say two pthreads concurrently call this:

void printSomething(){
    std::cout << "something\n";
}

What can cause the two outputs to be interleaved? Or will I get in practice always two lines that contain "something"?

I ask this because this question made me wonder and I found this answer stating:

... in C++11, std::cout is thread-safe.

but this answer presents an example along the line of

std::cout << "aaaaaaaaaa" << "bbbbbbbbbb";

and draws the conclusion that in C++98, two thread executing this might have interleaving output, but I couldnt find anything about two threads calling the operator<< only once.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 2
    so what's the actual question? is `std::cout` thread safe in C++98? no. it wasn't written at the time to be thread safe. C++98 knows nothing about threads. it's not "thread safe", it's simply non existent for the standard presective. – David Haim Oct 18 '17 at 20:56
  • 1
    C++98 has *no* concept/notion of threads. So whatever you do in that world (as far as threads go) is inherently 'implementation defined' (or worse). So the answer to your question has to be "no". – Jesper Juhl Oct 18 '17 at 20:57
  • 4
    To be explicit: Choose an implementation you are asking about. This is outside the scope of C++98 itself. – Baum mit Augen Oct 18 '17 at 20:58
  • @BaummitAugen you are right, but acutally it was just a general question with no particular use case in mind. Tbh "It is implementation defined" is already a satisfactory answer for me. I knew there is no guarantee, but I was missing the correct term. – 463035818_is_not_an_ai Oct 18 '17 at 21:01
  • @BaummitAugen I could change the question to "Do you know any implementation where the two outputs can get interleaved?" but that would probably be too broad and lead nowhere – 463035818_is_not_an_ai Oct 18 '17 at 21:02
  • I agree. Although the question isn't specific enough as it stands either. The entire question is outside the scope of the standard you are asking about. – Baum mit Augen Oct 18 '17 at 21:04
  • Interleaved output isn't the worst case. The internal state of `cout` could get mangled and undefined behavior results. – Mark Ransom Oct 18 '17 at 21:06
  • @BaummitAugen sometimes I almost know the answer, but have serious doubts and need some confirmation. To be really honest, whether it makes a well received question for SO I dont care too much in such cases (but of course I do highly appreciate any clarification and helpful comments) – 463035818_is_not_an_ai Oct 18 '17 at 21:08
  • 1
    What you can do is put a clarifier like "Is writing a single string to cout thread safe in C++98 _in typical implementations_". Of course, this will just result in language lawyers telling you that there is no such thing as a _typical implementation_ in the standard, etc, etc - but at least you might suss out a few answers from the more practical and less legally inclined posters. – BeeOnRope Oct 18 '17 at 21:14
  • Possible duplicate of [Is cout synchronized/thread-safe?](https://stackoverflow.com/q/6374264/608639) – jww May 27 '18 at 11:31

1 Answers1

6

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.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • changed the question slightly to make it less suspectible to language lawyer arguments. I think the edit doesnt change the fact that your answer fits nicely. It is much more than what i was hoping for btw :) – 463035818_is_not_an_ai Oct 18 '17 at 21:24
  • "After all, using that stream for output is very common", and in threaded environments is very common to see the odd message scrambled up (typical use case of cout is for trace / debug so nobody profoundly cares about the odd mess) – pm100 Oct 18 '17 at 22:42
  • @pm100 - agreed. My experience is that `std::cout` usually offers approximately the "single string is atomic guarantee", but separate values inserted on the same line are _often_ mixed. It actually means that `printf` gives a better experience since the entire line generally gets sent down at once. I often also see a lot of interleaving between `stdout` and `stderr` when they are both going to the same place: even when each is atomic, terminal implementations often like to mix them unpredictably. – BeeOnRope Oct 18 '17 at 22:53
  • @BeeOnRope: It's not the terminal that's to blame - it knows nothing about stdout vs stderr. Usually it will be the buffering in the standard library that is responsible for this behaviour. – psmears Oct 18 '17 at 22:58
  • @psmears - I don't agree, but maybe I should have said "the shell" not the terminal. The C++ process itself sees two totally different files, `stdout` and `stderr`: they aren't linked at all. The C++ library might write two totally consistent streams to each file (indeed, perhaps there are only two threads: one writing to `stdout` and one to `stderr), but because both are going to the same _display_ they get interleaved. It's up to the shell to mux them together at that point, isn't it? – BeeOnRope Oct 18 '17 at 23:06
  • @BeeOnRope: No, "the shell" would also be wrong. At the kernel level, a C/C++ process will see two file descriptors, 1 and 2, which (via various layers) it maps to objects it calls `stdout` and `stderr` respectively. As a general rule both file descriptors 1 and 2 will be opened on the same device, the terminal. In that case, kernel `write()` calls to either 1 or 2 will be serialised correctly. The issue is that typically the standard library will do buffering separately on each of `stdout` and `stderr`, which means that what the main program wrote sequentially can end up interleaved. – psmears Oct 18 '17 at 23:18
  • @psmears - OK, I think we agree here then. From the point of view of the standard library, however, it seems completely reasonable to implement buffering separately on each file - they are after all, separate files, and I wouldn't expect independent output to two _separate_ streams to have any particular relationship (indeed, it's common for `stderr` to use a different, more "immediate" buffering strategy so that output occurs earlier). All we can ask of the standard library, I think, is that output to separate, independent files appears consistent. – BeeOnRope Oct 18 '17 at 23:21
  • The problem then is that in the case that both streams are going to the _same place_, like the screen of the user's terminal, some process needs to read _independently_ from both files and "mux" the output together. Not all terminals do this in the same way, but the output often ends up interleaved. A typical example is that a terminal will have two threads reading from the child's `stderr` and `stdout` and when there is output just take a lock and write to the shared "screen buffer" or whatever abstraction they have for the screen. – BeeOnRope Oct 18 '17 at 23:23
  • Depending on exactly the size of the _read buffers in the consuming process_ and the various pipe sizes and such it may result in worse interleaving than you'd expect. I'm not saying the any particular terminal is doing the wrong thing BTW: its just that the terminal is in the hard spot of muxing the two streams, which the standard library in the generating process didn't even have to worry about. There is a tradeoff between prompt output and interleaving: some users might want line buffering, but not at the cost of indefinitely delaying output, etc. – BeeOnRope Oct 18 '17 at 23:25