簡體   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