繁体   English   中英

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

[英]thread safe std::cout using mutex

我尝试制作一些线程安全的std::cout ,对我来说最好的解决方案如下所示:

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

我想使用的是这样的:

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

但不幸的是我得到一个编译器错误:

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>’})
   

对我来说,这条消息是绝对不可读的。

我在这里做错了什么?

这里有几个问题。

首先, std::cout << out; 是错误的,因为对于这些操作数没有匹配的operator<<重载(这里out是一个std::ostream& )。 这基本上就是所谓的“不可读”错误消息所说的。

出于同样的原因,这同样适用于"hello" << std::endl

此外, std::endl是一个 function ,而且比模板化的 function 还要多。如果您想将它作为参数传递,则必须指定您需要的重载,在本例中, std::endl<char, std::char_traits<char>>

您可以通过以下方式简化符号:

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

这样,您就可以传递先前定义的endl function。


为了解决您的问题,我认为一个好的解决方案是将过程分为两个步骤:

  • 将您的数据累积到 stream(我将在此处使用std::stringstream
  • 打印累积的数据。

为此,您可以将所有机器隐藏在助手 class 中,我们称它为Printer

为了让它像你想要的那样灵活,我们可以使用可变参数模板。
然后语法将从"hello" << value <<...更改为"hello", value, ...

综上所述,我们可以将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
        }
};

注意:如果你在之前,你可以替换s.view(); s.str(); .

然后你可以按如下方式使用它:

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:

你好 !
再见 !

活生生的例子在这里


注意:使用std::scoped_lock (或std::lock_guard ,如果在之前)而不是传统的锁定/解锁机制会更安全,因为它使用 RAII 来确保在离开 scope(在例如,在发布之前抛出异常的情况)。

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

如果你想使用 function 之类的

print("hello" );

在 function 中你想要 output

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

那么您需要使用字符串文字“hello”作为 function 参数。

例如

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" );

或者可以将参数声明为具有类型std::string_view 例如

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;
}

好的,一个非常适合我的解决方案是使用上面建议的缓冲 output。

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

我用 10 个线程测试了这个结构,这些线程经常打印输出到控制台。

你可以用宏来做到这一点。

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

然后,在您的代码中替换时:

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;
}

注意:如果您不想在消息后添加自动 endl,可以将其替换为std::flush

注意:在这种代码中,您需要使用作用域锁而不是锁定/解锁指令,以便即使 stream 用法或message中调用的函数抛出异常也能解锁。

注意: do {} while(false) 在宏中使用,因为它是唯一可以被编译器视为唯一指令的 scope。 当在没有大括号的循环中使用时,它避免了潜在的错误。

暂无
暂无

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

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