开发者

thread safe streams and stream manipulators

开发者 https://www.devze.com 2023-02-22 05:07 出处:网络
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.

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

  1. log<<"asdfg";

    I have to instead do

    log<<string("asdfg");

  2. 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;
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号