[英]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.