How to easily make std::cout thread-safe?

前端 未结 9 710
庸人自扰
庸人自扰 2020-12-02 10:52

I have a multi-threaded application, which heavily uses std::cout for logging without any locking. In such a case, how can I easily add lock mechanism to make <

相关标签:
9条回答
  • 2020-12-02 11:14

    A feasible solution uses a line-buffer for each thread. You might get interleaved lines, but not interleaved characters. If you attach that to thread-local storage, you also avoid lock contention issues. Then, when a line is full (or on flush, if you want), you write it to stdout. This last operation of course has to use a lock. You stuff all this into a streambuffer, which you put between std::cout and it's original streambuffer.

    The problem this doesn't solve is things like format flags (e.g. hex/dec/oct for numbers), which can sometimes percolate between threads, because they are attached to the stream. It's nothing bad, assuming you're only logging and not using it for important data. It helps to just not format things specially. If you need hex output for certain numbers, try this:

    template<typename integer_type>
    std::string hex(integer_type v)
    {
        /* Notes:
        1. using showbase would still not show the 0x for a zero
        2. using (v + 0) converts an unsigned char to a type
           that is recognized as integer instead of as character */
        std::stringstream s;
        s << "0x" << std::setfill('0') << std::hex
            << std::setw(2 * sizeof v) << (v + 0);
        return s.str();
    }
    

    Similar approaches work for other formats as well.

    0 讨论(0)
  • 2020-12-02 11:16

    Note: This answer is pre-C++20 so it does not use std::osyncstream with its separate buffering, but uses a lock instead.

    I guess you could implement your own class which wraps cout and associates a mutex with it. The operator << of that new class would do three things:

    1. create a lock for the mutex, possibly blocking other threads
    2. do the output, i.e. do the operator << for the wrapped stream and the passed argument
    3. construct an instance of a different class, passing the lock to that

    This different class would keep the lock and delegate operator << to the wrapped stream. The destructor of that second class would eventually destroy the lock and release the mutex.

    So any output you write as a single statement, i.e. as a single sequence of << invocations, will be printed atomically as long as all your output goes through that object with the same mutex.

    Let's call the two classes synchronized_ostream and locked_ostream. If sync_cout is an instance of synchronized_ostream which wraps std::cout, then the sequence

    sync_cout << "Hello, " << name << "!" << std::endl;
    

    would result in the following actions:

    1. synchronized_ostream::operator<< would aquire the lock
    2. synchronized_ostream::operator<< would delegate the printing of "Hello, " to cout
    3. operator<<(std::ostream&, const char*) would print "Hello, "
    4. synchronized_ostream::operator<< would construct a locked_ostream and pass the lock to that
    5. locked_ostream::operator<< would delegate the printing of name to cout
    6. operator<<(std::ostream&, std::string) would print the name
    7. The same delegation to cout happens for the exclamation point and the endline manipulator
    8. The locked_ostream temporary gets destructed, the lock is released
    0 讨论(0)
  • 2020-12-02 11:19

    Along the lines of the answer suggested by Conchylicultor, but without inheriting from std::ostringstream:


    EDIT: Fixed return type for the overloaded operator and added overload for std::endl.


    EDIT 1: I have extended this into a simple header-only library for logging / debugging multi-threaded programs.


    #include <iostream>
    #include <mutex>
    #include <thread>
    #include <vector>    
    #include <chrono>
    
    static std::mutex mtx_cout;
    
    // Asynchronous output
    struct acout
    {
            std::unique_lock<std::mutex> lk;
            acout()
                :
                  lk(std::unique_lock<std::mutex>(mtx_cout))
            {
    
            }
    
            template<typename T>
            acout& operator<<(const T& _t)
            {
                std::cout << _t;
                return *this;
            }
    
            acout& operator<<(std::ostream& (*fp)(std::ostream&))
            {
                std::cout << fp;
                return *this;
            }
    };
    
    int main(void)
    {
    
    
        std::vector<std::thread> workers_cout;
        std::vector<std::thread> workers_acout;
    
        size_t worker(0);
        size_t threads(5);
    
    
        std::cout << "With std::cout:" << std::endl;
    
        for (size_t i = 0; i < threads; ++i)
        {
            workers_cout.emplace_back([&]
            {
                std::cout << "\tThis is worker " << ++worker << " in thread "
                          << std::this_thread::get_id() << std::endl;
            });
        }
        for (auto& w : workers_cout)
        {
            w.join();
        }
    
        worker = 0;
    
        std::this_thread::sleep_for(std::chrono::seconds(2));
    
        std::cout << "\nWith acout():" << std::endl;
    
        for (size_t i = 0; i < threads; ++i)
        {
            workers_acout.emplace_back([&]
            {
                acout() << "\tThis is worker " << ++worker << " in thread "
                        << std::this_thread::get_id() << std::endl;
            });
        }
        for (auto& w : workers_acout)
        {
            w.join();
        }
    
        return 0;
    }
    

    Output:

    With std::cout:
            This is worker 1 in thread 139911511856896
            This is worker  This is worker 3 in thread 139911495071488
            This is worker 4 in thread 139911486678784
    2 in thread     This is worker 5 in thread 139911503464192139911478286080
    
    
    With acout():
            This is worker 1 in thread 139911478286080
            This is worker 2 in thread 139911486678784
            This is worker 3 in thread 139911495071488
            This is worker 4 in thread 139911503464192
            This is worker 5 in thread 139911511856896
    
    0 讨论(0)
提交回复
热议问题