简体   繁体   English

使用互斥量的线程安全 std::cout

[英]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:我尝试制作一些线程安全的std::cout ,对我来说最好的解决方案如下所示:

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;首先, std::cout << out; is wrong because there is no matching overload for operator<< for those operands ( out is a std::ostream& here).是错误的,因为对于这些操作数没有匹配的operator<<重载(这里out是一个std::ostream& )。 This is basically what the so called "unreadable" error message was saying.这基本上就是所谓的“不可读”错误消息所说的。

The same applies with "hello" << std::endl for the same reason.出于同样的原因,这同样适用于"hello" << std::endl

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>> .此外, std::endl是一个 function ,而且比模板化的 function 还要多。如果您想将它作为参数传递,则必须指定您需要的重载,在本例中, 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.这样,您就可以传递先前定义的endl function。


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)将您的数据累积到 stream(我将在此处使用std::stringstream
  • Print the accumulated data.打印累积的数据。

For this purpose, you could hide all the machinery inside a helper class, let's call it Printer .为此,您可以将所有机器隐藏在助手 class 中,我们称它为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, ... .然后语法将从"hello" << value <<...更改为"hello", value, ...

To sum it up, we can have the definition of the Printer class as:综上所述,我们可以将Printer class 定义为:

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();注意:如果你在之前,你可以替换s.view(); with s.str();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: 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).注意:使用std::scoped_lock (或std::lock_guard ,如果在之前)而不是传统的锁定/解锁机制会更安全,因为它使用 RAII 来确保在离开 scope(在例如,在发布之前抛出异常的情况)。

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(...);注意 2:如果您不想打扰Printer实例,则可以将其中的所有内容声明为static ,这样您就可以直接使用Printer::print(...); . .

If you want to use the function like如果你想使用 function 之类的

print("hello" );

and within the function you want to output在 function 中你想要 output

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

then you need to use the string literal "hello" as the function argument.那么您需要使用字符串文字“hello”作为 function 参数。

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 .或者可以将参数声明为具有类型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.好的,一个非常适合我的解决方案是使用上面建议的缓冲 output。

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.我用 10 个线程测试了这个结构,这些线程经常打印输出到控制台。

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 .注意:如果您不想在消息后添加自动 endl,可以将其替换为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 .注意:在这种代码中,您需要使用作用域锁而不是锁定/解锁指令,以便即使 stream 用法或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.注意: do {} while(false) 在宏中使用,因为它是唯一可以被编译器视为唯一指令的 scope。 It avoid insidious bugs when used in a loop without braces.当在没有大括号的循环中使用时,它避免了潜在的错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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