iostream thread safety, must cout and cerr be locked separately?
Asked Answered
U

6

11

I understand that to avoid output intermixing access to cout and cerr by multiple threads must be synchronized. In a program that uses both cout and cerr, is it sufficient to lock them separately? or is it still unsafe to write to cout and cerr simultaneously?

Edit clarification: I understand that cout and cerr are "Thread Safe" in C++11. My question is whether or not a write to cout and a write to cerr by different threads simultaneously can interfere with each other (resulting in interleaved input and such) in the way that two writes to cout can.

Ulphiah answered 1/2, 2013 at 0:16 Comment(5)
It's never "unsafe". You may just not get what you expect.Accordingly
I guess to clarify then. Is there a difference in behavior between using one global lock for writes to both cout and cerr vs a separate lock for each?Ulphiah
you can use different locks. they don't depend on each other.Sgraffito
Perhaps this question would be better phrased as, "May cout and cerr be locked separately?"Rearmost
Also see Is cout synchronized/thread-safe?Intransigence
I
12

If you execute this function:

void f() {
    std::cout << "Hello, " << "world!\n";
}

from multiple threads you'll get a more-or-less random interleaving of the two strings, "Hello, " and "world\n". That's because there are two function calls, just as if you had written the code like this:

void f() {
    std::cout << "Hello, ";
    std::cout << "world!\n";
}

To prevent that interleaving, you have to add a lock:

std::mutex mtx;
void f() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Hello, " << "world!\n";
}

That is, the problem of interleaving has nothing to do with cout. It's about the code that uses it: there are two separate function calls inserting text, so unless you prevent multiple threads from executing the same code at the same time, there's a potential for a thread switch between the function calls, which is what gives you the interleaving.

Note that a mutex does not prevent thread switches. In the preceding code snippet, it prevents executing the contents of f() simultaneously from two threads; one of the threads has to wait until the other finishes.

If you're also writing to cerr, you have the same issue, and you'll get interleaved output unless you ensure that you never have two threads making these inserter function calls at the same time, and that means that both functions must use the same mutex:

std::mutex mtx;
void f() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "Hello, " << "world!\n";
}

void g() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cerr << "Hello, " << "world!\n";
}
Illtreat answered 1/2, 2013 at 15:23 Comment(2)
It should be noted that if the OS directs both cout and cerr to the same source (like... the console), then the text will be interleaved, unless they share the same mutex.Deservedly
The OS and libraries have a lot to do with it. Threads in Linux can output to stdout all they like, no locking, yet individual lines of text will not get scrambled by another thread's stdout. Output to stderr is not buffered in the same way, so it will get scambled up. This difference in behaviour causes problems in some versions of Eclipse CDT; compiler errors come out on stderr and do not appear in the right place wrt the stdout from the compiler in Eclipse's console.Renner
B
9

In C++11, unlike in C++03, the insertion to and extraction from global stream objects (cout, cin, cerr, and clog) are thread-safe. There is no need to provide manual synchronization. It is possible, however, that characters inserted by different threads will interleave unpredictably while being output; similarly, when multiple threads are reading from the standard input, it is unpredictable which thread will read which token.

Thread-safety of the global stream objects is active by default, but it can be turned off by invoking the sync_with_stdio member function of the stream object and passing false as an argument. In that case, you would have to handle the synchronization manually.

Breuer answered 1/2, 2013 at 0:27 Comment(0)
S
5

It may be unsafe to write to cout and cerr simultaneously ! It depends on wheter cout is tied to cerr or not. See std::ios::tie.

"The tied stream is an output stream object which is flushed before each i/o operation in this stream object."

This means, that cout.flush() may get called unintentionally by the thread which writes to cerr. I spent some time to figure out, that this was the reason for randomly missing line endings in cout's output in one of my projects :(

With C++98 cout should not be tied to cerr. But despite the standard it is tied when using MSVC 2008 (my experience). When using the following code everything works well.

std::ostream *cerr_tied_to = cerr.tie();
if (cerr_tied_to) {
    if (cerr_tied_to == &cout) {
        cerr << "DBG: cerr is tied to cout ! -- untying ..." << endl;
        cerr.tie(0);
    }
}

See also: why cerr flushes the buffer of cout

Seidel answered 9/2, 2014 at 23:23 Comment(2)
Interesting. To be sure, this isn't unsafe as UB, it's "only" behaving badly.Rearmost
Wow... this caused me some hard to spot bugs.Writeup
R
1

There are already several answers here. I'll summarize and also address interactions between them.

Typically,

std::cout and std::cerr will often be funneled into a single stream of text, so locking them in common results in the most usable program.

If you ignore the issue, cout and cerr by default alias their stdio counterparts, which are thread-safe as in POSIX, up to the standard I/O functions (C++14 §27.4.1/4, a stronger guarantee than C alone). If you stick to this selection of functions, you get garbage I/O, but not undefined behavior (which is what a language lawyer might associate with "thread safety," irrespective of usefulness).

However, note that while standard formatted I/O functions (such as reading and writing numbers) are thread-safe, the manipulators to change the format (such as std::hex for hexadecimal or std::setw for limiting an input string size) are not. So, one can't generally assume that omitting locks is safe at all.

If you choose to lock them separately, things are more complicated.

Separate locking

For performance, lock contention may be reduced by locking cout and cerr separately. They're separately buffered (or unbuffered), and they may flush to separate files.

By default, cerr flushes cout before each operation, because they are "tied." This would defeat both separation and locking, so remember to call cerr.tie( nullptr ) before doing anything with it. (The same applies to cin, but not to clog.)

Decoupling from stdio

The standard says that operations on cout and cerr do not introduce races, but that can't be exactly what it means. The stream objects aren't special; their underlying streambuf buffers are.

Moreover, the call std::ios_base::sync_with_stdio is intended to remove the special aspects of the standard streams — to allow them to be buffered as other streams are. Although the standard doesn't mention any impact of sync_with_stdio on data races, a quick look inside the libstdc++ and libc++ (GCC and Clang) std::basic_streambuf classes shows that they do not use atomic variables, so they may create race conditions when used for buffering. (On the other hand, libc++ sync_with_stdio effectively does nothing, so it doesn't matter if you call it.)

If you want extra performance regardless of locking, sync_with_stdio(false) is a good idea. However, after doing so, locking is necessary, along with cerr.tie( nullptr ) if the locks are separate.

Rearmost answered 15/3, 2016 at 5:48 Comment(1)
If you do want separate locking, I've created a library for it.Rearmost
C
1

This may be useful ;)

inline static void log(std::string const &format, ...) {
    static std::mutex locker;

    std::lock_guard<std::mutex>(locker);

    va_list list;
    va_start(list, format);
    vfprintf(stderr, format.c_str(), list);
    va_end(list);
}
Churchwell answered 2/6, 2018 at 22:40 Comment(0)
C
0

I use something like this:

// Wrap a mutex around cerr so multiple threads don't overlap output
// USAGE:
//     LockedLog() << a << b << c;
// 
class LockedLog {
public:
    LockedLog() { m_mutex.lock(); }
    ~LockedLog() { *m_ostr << std::endl; m_mutex.unlock(); }

    template <class T>
    LockedLog &operator << (const T &msg)
    {
        *m_ostr << msg;
        return *this;
    }

private:
    static std::ostream *m_ostr;
    static std::mutex m_mutex;
};

std::mutex LockedLog::m_mutex;
std::ostream* LockedLog::m_ostr = &std::cerr;
Clipfed answered 6/2, 2019 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.