Thread safe cout technique. Am I missing something?
Asked Answered
C

1

10

I'm working with some multithreaded code for a game project, and got a bit tired of sorting through the stdout vomit created by two threads using cout for debuging messages at the same time. I did some research and stared at a wall for an hour or two before coming up with "something". The following code uses SFML for time keeping and threading. SFML mutexes are just wrapped critical sections in windows.

Header:

#include <SFML\System.hpp>
#include <iostream>

class OutputStreamHack
{
    public:
    OutputStreamHack();
    ~OutputStreamHack();

    ostream& outputHijack(ostream &os);

    private:
    sf::Clock myRunTime;
    sf::Mutex myMutex;
};

static OutputStream OUTHACK;

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue);

Implementation:

#include <SFML\System.hpp>
#include <iostream>

#include "OutputStreamHack.h"

using namespace std;

OutputStreamHack::OutputStreamHack()
{
    myMutex.Unlock();
    myRunTime.Reset();
}

OutputStreamHack::~OutputStreamHack()
{
    myMutex.Unlock();
    myRunTime.Reset();
}

ostream& OutputStreamHack::outputHijack(ostream &os)
{

    sf::Lock lock(myMutex);
    os<<"<"<<myRunTime.GetElapsedTime()<<","<<GetCurrentThreadId()<<"> "<<flush;
    return os;
}

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue)
{
    OUTHACK.outputHijack(os);
    return os;
}

Usage:

cout<<OUTHACK<<val1<<val2<<val3....<<endl;

Ok, so the way this works is through an overloaded insertion operator that imposes thread safety by locking an iterator in a static object, then flushing the buffer. If I understand the process correctly (I am mostly a self taught programmer), cout processes elements of its insertion chain from the end to the beginning, passing an ostream variable down the chain for each element to be prepended to the stream. Once it reaches the OUTHACK element, the overloaded operator is called, the mutex is locked, and the stream is flushed.

I've added some time/thread id debugging info to the output for verification purposes. So far, my testing shows that this method works. I have several threads pounding cout with multiple arguments, and everything is coming out in the right order.

From what I have read while researching this issue, lack of thread safety in cout seems to be a pretty common problem that people run into while venturing into threaded programming. What I am trying to figure out is if the technique I am using is a simple solution to the problem, or me thinking that I am clever but missing something important.

In my experience, the word clever when used to describe programming is just a code word for delayed pain. Am I on to something here, or just chasing lousy hacks around in circles?

Thanks!

Cesura answered 2/3, 2012 at 2:21 Comment(3)
The fact that it works is pure luck. Only the output of the time and thread id is protected by the mutex. It's perfectly possible to get another thread sneak in between OUTHACK and val1.Extramarital
Consider writing to an ostringstream first, then dumping the content in one operation to cout. cout will generally be thread safe, it's just that the locking is obviously per call, and each << operation is a distinct call.... That way, there's no extra locking in your application code - which can only reduce parallelism.Condescendence
Related: Is cout synchronised/thread-safe?Kal
E
22

What is not threadsafe here is not cout per se. It's calling two function calls in sequence. std::cout << a << b is roughly equivalent to calling operator<<(std::cout, a) followed by operator<<(std::cout, b). Calling two functions in sequence carries no guarantee that they will be executed in an atomic fashion.

As is, only the output of the time and thread id is protected by the mutex. It's perfectly possible to get another thread sneak in between the insertion of OUTHACK and val1, because the lock is no longer held after OUTHACK is inserted.

You can have operator<< for your OutputStreamHack return by value an object that unlocks in the destructor. Since temporaries live until the end of each full expression, the code would hold the lock "until the semicolon". However, because copies may be involved, this could be problematic without a move constructor (or a custom copy constructor in C++03, similar to auto_ptr's gasp).

Another option is to use the existing thread-safety of cout (guaranteed by the language in C++11, but many implementations were threadsafe before). Make an object that streams everything into a std::stringstream member and then write it all out at once when it is destroyed.

class FullExpressionAccumulator {
public:
    explicit FullExpressionAccumulator(std::ostream& os) : os(os) {}
    ~FullExpressionAccumulator() {
        os << ss.rdbuf() << std::flush; // write the whole shebang in one go
    }

    template <typename T>
    FullExpressionAccumulator& operator<<(T const& t) {
        ss << t; // accumulate into a non-shared stringstream, no threading issues
        return *this;
    }

private:
    std::ostream& os;
    std::stringstream ss;

    // stringstream is not copyable, so copies are already forbidden
};

// using a temporary instead of returning one from a function avoids any issues with copies
FullExpressionAccumulator(std::cout) << val1 << val2 << val3;
Extramarital answered 2/3, 2012 at 2:30 Comment(2)
Ah, I see the gap in my thinking. I was operating under the assumption that the cout behavior was recursive rather than iterative, and that it was working on a local stream that was merged into stdout at the point where OUTHACK was processed. Your technique looks like exactly what I was missing! Thanks a ton.Cesura
This solution can be used to mimic std::osyncstream (which is included with but not yet available for C++20), however, it still requires a mutex to prevent interleaved/garbled output and at least 1 more operator<< overload to account for std::endl.Ethiopia

© 2022 - 2024 — McMap. All rights reserved.