I have a simple logger written, I have a problem.
pthread_mutex_t l_mutex = PTHREAD_MUTEX_INITIALIZER;
class logger
{
public:
logger() { }
template<typename T>
logger& operator<<(const T& t)
{
pthread_mutex_lock(&l_mutex);
std::cout << t << std::flush;
pthread_mutex_unlock(&l_mutex);
return *this;
}
protected:
};
static const unsigned char endline = '\n';
static logger log;
When I use the logger however from two threads, it does exactly what it shouldn't do. The calls to;
Thread #1
log << "Hello " << 1;
Thread #2
log << "Goodbye " << 2;
The output gets interleaved because of the way operator<< is implemented. How would I be able to get natural use of << AND have it be atomic in nature so instead of
Hello 2 Goodbye 1
We are guaranteed to get
Hello 1 Goodbye 2
Thanks in advance. Is there a silver bullet there or is a new approach required?
Thanks
G
Use a helper object.
Change your existing <<
overload method to return a helper object, something like:
template<typename T>
loggerHelper operator<<(const T& t)
{
return loggerHelper{} << t;
}
Your loggerHelper
class must have the following properties:
its constructor should acquire the mutex. And the destructor will unlock the mutex. Run-of-the-mill RAII design pattern, but with one additional twist:
the object must correctly, and precisely, implement move semantics. The moved-from object instance will not release the aforementioned mutex, the moved-to object will be responsible for that. The copy constructor and the assignment operator must be delete
d.
the object implements the "real" <<
operator overload, using std::cout
and returns *this
. This will trigger move semantics, and passing the "hot potato" of releasing the mutex to the next <<
operator invocation, or if there is none, finally destroying the end-of-the-road helper class instance.
If all these steps are done correctly, then I would expect the end result will be the mutex on the main logger
object getting acquired once, and not released until all the daisy-chained <<
operators are finished with their responsibilities.
The first <<
operator gets kicked off by the original <<
operator, above, and they all get processed, for real, by the helper object's <<
operator overload, with move semantics taking care of preserving the underlying mutex lock, and releasing it at the end.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.