[英]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。
为了解决您的问题,我认为一个好的解决方案是将过程分为两个步骤:
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
}
};
注意:如果你在c++20之前,你可以替换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
,如果在c++17之前)而不是传统的锁定/解锁机制会更安全,因为它使用 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.