thread safe streams and stream manipulators
I am trying to write a thread safe logger class so that i can do the exact same as with cout but with thread safety.
here is the logger class (still working on the type of lock required)
class logger {
public:
logger(LOGGER::output_type type);
logger(const logger& orig);
virtual ~logger();
template <typename T>
logger & operator << (const T &开发者_JAVA技巧; data){
boost::mutex::scoped_lock io_mutex_lock(io_mutex);
(*out)<<data;
return *this;
}
private:
static boost::mutex io_mutex;
std::ostream * out;
};
The poblem is I cannot do the following
I have to instead dolog<<"asdfg";
log<<string("asdfg");
int i = 10;
log<<string ("i = ") << i << endl;
following is the compilation error.
gcc.compile.c++ src/simpleThread/bin/gcc-4.4.5/debug/simpleThread.o
src/simpleThread/simpleThread.cc: In function ‘int main()’:
src/simpleThread/simpleThread.cc:28: error: no match for ‘operator<<’ in ‘((logger*)logOut.logger::operator<< [with T = char [18]](((const char (&)[18])"fibonacci thread ")))->logger::operator<< [with T = int](((const int&)((const int*)(& i)))) << std::endl’
So I guess i am missing some important concept of C++. Please let me know what it is? Is my requirement even achievable
thanks, Kiran
Note that your logger class is still not thread safe:
int i = 10;
log <<string ("i = ") << i << endl;
There is nothing stopping this thread from getting preempted by another another thread printing to logger and producing something like:
i = i = 12
Instead of:
i = 1
i = 2
If you have a compiler with variadic templates, here's one way of fixing this:
#include <ostream>
#include <mutex>
inline void sub_print(std::ostream&) {}
template <class A0, class ...Args>
void
sub_print(std::ostream& os, const A0& a0, const Args& ...args)
{
os << a0;
sub_print(os, args...);
}
std::mutex&
io_mut()
{
static std::mutex m;
return m;
}
template <class ...Args>
void
log(std::ostream& os, const Args& ...args)
{
std::lock_guard<std::mutex> _(io_mut());
sub_print(os, args...);
os.flush();
}
#include <iostream>
int main()
{
int i = 10;
log(std::cout, "i = ", i, '\n');
}
I.e. the mutex is locked until all arguments for given log message are processed. std::endl
is handled separately by always flushing the stream after every message.
Your problem is that logger
isn't a stream, so the usual stream operators will not work, just the single one you have defined.
You can get endl
to work by defining it yourself:
inline logger& endl(logger& log)
{
// do something to *out
return log;
}
Now log << "Hello!" << endl;
will work.
To be able to chain several << operations together like the streams do, you will have to define all the operators for the logger (just like the streams do).
I think you are introducing synchronization at too low a level. To understand why, assume thread 1 executes:
log << "This is " << "my " << "log message" << endl;
while thread 2 executes:
log << "Hello, " << "World!" << endl;
In such a case, the log file (or console output) may contain interleaved messages, for example:
This is Hello, my World!
log message
To avoid this problem, your application will have to construct an entire message as a single string, and only then pass that string to a logger object. For example:
ostringstream msg;
msg << "This is " << "my " << "log message" << endl;
log << msg.str();
If you take this approach, then your logger class does not need to overload operator<<
for endl
and multiple types.
I tested your program with some simplifications as follows, and it compiles and runs fine, which means that the problem is probably elsewhere:
#include <iostream>
class logger {
public:
// logger(LOGGER::output_type type);
logger(std::ostream& os): out(&os) {}
~logger() {}
template <typename T>
logger & operator << (const T & data){
// boost::mutex::scoped_lock io_mutex_lock(io_mutex);
(*out)<<data;
return *this;
}
private:
// static boost::mutex io_mutex;
std::ostream * out;
};
int main()
{
logger log(std::cout);
log << std::string("hello ");
log << "world\n";
}
After digging into iostreams and with hints from Bo Persson, I think i found a better solutions since I dont need to write a function each for each ios manipulator. So here it is
logger& operator<<(std::ostream& (*pf)(std::ostream&)) {
(*out)<<pf;
return *this;
}
For an explanation search for iostreams and applicators.
Here is the complete boost::thread safe implementation (requires some refactoring and optimization probably) using some hints from Ciaran-Mchale
/*
* File: logger.h
* Author: Kiran Mohan
*
*/
#ifndef LOGGER_H
#define LOGGER_H
#include <boost/thread.hpp>
#include <iostream>
namespace LOG {
enum output_type {
STDOUT,
STDERR
};
/**
* a thread safe logger to print to stdout or stderr
*/
class logger {
public:
logger(LOG::output_type type);
logger(const logger& orig);
virtual ~logger();
template <typename T>
logger & operator <<(T data) {
/* Takes any data type and stores in a stringstream
*/
(*oss) << data;
return *this;
}
logger & operator<<(std::ostream& (*pf)(std::ostream&)) {
// for stream manipulators
(*oss) << pf;
return *this;
}
logger & operator<<(logger & (*pf)(logger &)) {
//applicator - mainly calling the print function;
return pf(*this);
}
friend logger & flush(logger & l);
logger & print() {
boost::mutex::scoped_lock io_mutex_lock(io_mutex);
(*out) << oss->str() << std::flush;
delete oss;
oss = new std::ostringstream;
return *this;
}
private:
static boost::mutex io_mutex;
std::ostream * out;
std::ostringstream * oss;
};
logger & flush(logger & l);
};
#endif /* LOGGER_H */
/*
* File: logger.cc
* Author: aryan
*
*/
#include <boost/thread/pthread/mutex.hpp>
#include <iostream>
#include "logger.h"
using namespace LOG;
boost::mutex logger::io_mutex;
logger::logger(LOG::output_type type) {
if (type == LOG::STDOUT) {
out = &std::cout;
} else {
out = &std::cerr;
}
oss = new std::ostringstream;
}
logger::logger(const logger& orig) {
out = orig.out;
}
logger::~logger() {
delete oss;
}
logger & LOG::flush(logger & l) {
l.print();
boost::this_thread::yield();
return l;
}
use it like this
LOG::logger logOut (LOG::STDOUT);
logOut<<"Hello World\n"<<LOG::flush;
精彩评论