简体   繁体   中英

thread safe std::cout using mutex

I try to make some thread safe std::cout and the best solution for me will look like the following:

void print(std::ostream &out) 
{
    pthread_mutex_lock(&m_mutex);
    std::cout << out;
    pthread_mutex_unlock(&m_mutex);
}

I want to use is like this:

print("hello" << std::endl);

but unfortunately I get a compiler error:

test.cpp:38: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::ostream’ {aka ‘std::basic_ostream<char>’})
test.cpp: In function ‘void print(std::ostream&)’:
test.cpp:38:15: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::ostream’ {aka ‘std::basic_ostream<char>’})
   

as for me this message is absolutely unreadable.

what I do wrong here?

There are several issues here.

First, std::cout << out; is wrong because there is no matching overload for operator<< for those operands ( out is a std::ostream& here). This is basically what the so called "unreadable" error message was saying.

The same applies with "hello" << std::endl for the same reason.

Moreover, std::endl is a function and more that than, a templated function. If you ever wanted to pass it as an argument, you will have to specify what overload you need, in this case, std::endl<char, std::char_traits<char>> .

You can simplify the notation the following way:

auto endl = std::endl<char, std::char_traits<char>>;

This way, you can pass the previoulsy defined endl function instead.


To solve your issue, I think a good solution would be to separate the process in two steps:

  • Accumulate your data into a stream (I will use std::stringstream here)
  • Print the accumulated data.

For this purpose, you could hide all the machinery inside a helper class, let's call it Printer .

To make it as flexible as you wanted it to be, we can make use of variadic templates.
The syntax would then be changed from "hello" << value <<... to "hello", value, ... .

To sum it up, we can have the definition of the Printer class as:

class Printer final
{
    private:
        std::stringstream s;

        template <typename T>
        void accumulate(T && t)
        {
            s << std::forward<T>(t);
        }

        template <typename T, typename ... Ts>
        void accumulate(T && t, Ts && ... ts)
        {
            s << std::forward<T>(t);
            accumulate(std::forward<Ts>(ts)...);
        }

    public:
        template <typename ... Ts>
        void print(Ts && ... ts)
        {
            //lock

            accumulate(std::forward<Ts>(ts)...);
            std::cout << s.view(); // Use s.str() instead if before C++20

            s.str(std::string());
            s.clear();

            //unlock
        }
};

Note: If you are before , you may replace s.view(); with s.str(); .

Then you can use it as follows:

int main()
{
    auto endl = std::endl<char, std::char_traits<char>>;

    std::string val("Bye !");

    Printer p;
    p.print("Hello", " !", '\n', val, endl);

    return 0;
}

Output:

Hello !
Bye !

Live example here


Note: It would be safer to use std::scoped_lock (or std::lock_guard if before ) instead of traditional lock/unlock mechanism since it makes use of RAII to ensure the mutex is released when going out of the scope (in the case an exception is thrown before the release for example).

Note 2: If you don't want to bother with a Printer instance, you can declare everything inside as static so that you could directly use Printer::print(...); .

If you want to use the function like

print("hello" );

and within the function you want to output

std::cout << "hello" << std::endl;

then you need to use the string literal "hello" as the function argument.

For example

std::ostream & print( const std::string &s, std::ostream &out = std::cout ) 
{
    pthread_mutex_lock(&m_mutex);
    out << s << std::endl;
    pthread_mutex_unlock(&m_mutex);

    return out;
}

//...

print("hello" );

Or the parameter can be declared as having the type std::string_view . For example

std::ostream & print( std::string_view s, std::ostream &out = std::cout ) 
{
    pthread_mutex_lock(&m_mutex);
    out << s << std::endl;
    pthread_mutex_unlock(&m_mutex);

    return out;
}

Ok, a solution that suits me perfectly is using a buffered output as suggested above.

std::stringstream stream;
stream << "value " << some_string << " is " << some_value << std::endl;
std::cout << stream.str();

I've tested this construction with 10 threads that print outs to console quite often.

You can do it with a maccro.

#define PRINT(out, message) do { scoped_lock(); out << message << std::endl; } while(false)

Then, when replaced in your code:

PRINT("hello !");
// actually is equivalent to
{
  scoped_lock();
  out << "hello !" << std::endl;
}

PRINT("a = " << a << ", b = " << b << std::endl
   << "total = " << compute_total_value());
// actually is equavalent to
{
  scoped_lock();
  out << "a = " << a << ", b = " << b << std::endl
      << "total = " << compute_total_value() << std::endl;
}

for(int i = 0; i < 10; ++i)
  PRINT("i = " << i);
// actually is equavalent to
for(int i = 0; i < 10; ++i)
{
  scoped_lock();
  out << "i = " << i << std::endl;
}

Note: if you don't want to have the automatic endl added after your message, you can replace it by std::flush .

Note: in this kind of code, you need to used a scoped lock instead of lock/unlock instructions, in order to unlock even if an exception was thrown by the stream usage or by the functions called in message .

Note: do {} while(false) is used in the maccro, because it's the only scope that can be considered as a unique instruction by the compiler. It avoid insidious bugs when used in a loop without braces.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM