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!
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With