C++ 如何轻松使 std::cout 线程安全?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14718124/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:37:46  来源:igfitidea点击:

How to easily make std::cout thread-safe?

c++multithreadinglogginglockingiostream

提问by xmllmx

I have a multi-threaded application, which heavily uses std::coutfor logging without any locking. In such a case, how can I easily add lock mechanism to make std::coutthread-safe?

我有一个多线程应用程序,它大量std::cout用于没有任何锁定的日志记录。在这种情况下,如何轻松添加锁机制以实现std::cout线程安全?

I don't want to search for each occurrence of std::coutand add a line of locking code. That is too tedious.

我不想搜索每次出现std::cout并添加一行锁定代码。那太乏味了。

Any better practice?

有什么更好的做法吗?

采纳答案by MvG

Note: This answer is pre-C++20 so it does not use std::osyncstreamwith its separate buffering, but uses a lock instead.

注意:这个答案是 C++20 之前的,所以它不使用std::osyncstream单独的缓冲,而是使用锁。

I guess you could implement your own class which wraps coutand associates a mutex with it. The operator <<of that new class would do three things:

我想你可以实现你自己的类,它包装cout和关联一个互斥锁。在operator <<新课改的会做三件事情:

  1. create a lock for the mutex, possibly blocking other threads
  2. do the output, i.e. do the operator <<for the wrapped stream and the passed argument
  3. construct an instance of a differentclass, passing the lock to that
  1. 为互斥锁创建一个锁,可能会阻塞其他线程
  2. 做输出,即为<<包装的流和传递的参数做操作符
  3. 构造一个不同类的实例,将锁传递给那个

This different class would keep the lock and delegate operator <<to the wrapped stream. The destructor of that second class would eventually destroy the lock and release the mutex.

这个不同的类会将锁和委托操作符保留<<给包装的流。第二个类的析构函数最终会破坏锁并释放互斥锁。

So any output you write as a single statement, i.e. as a single sequence of <<invocations, will be printed atomically as long as all your output goes through that object with the same mutex.

因此,您作为单个语句编写的任何输出,即作为单个<<调用序列,只要您的所有输出通过具有相同互斥锁的对象,就会自动打印。

Let's call the two classes synchronized_ostreamand locked_ostream. If sync_coutis an instance of synchronized_ostreamwhich wraps std::cout, then the sequence

让我们将这两个类称为synchronized_ostreamlocked_ostream。如果sync_coutsynchronized_ostreamwraps的实例std::cout,则序列

sync_cout << "Hello, " << name << "!" << std::endl;

would result in the following actions:

将导致以下操作:

  1. synchronized_ostream::operator<<would aquire the lock
  2. synchronized_ostream::operator<<would delegate the printing of "Hello, " to cout
  3. operator<<(std::ostream&, const char*)would print "Hello, "
  4. synchronized_ostream::operator<<would construct a locked_ostreamand pass the lock to that
  5. locked_ostream::operator<<would delegate the printing of nameto cout
  6. operator<<(std::ostream&, std::string)would print the name
  7. The same delegation to couthappens for the exclamation point and the endline manipulator
  8. The locked_ostreamtemporary gets destructed, the lock is released
  1. synchronized_ostream::operator<<会获得锁
  2. synchronized_ostream::operator<<会将“Hello,”的打印委托给 cout
  3. operator<<(std::ostream&, const char*)会打印“你好,”
  4. synchronized_ostream::operator<<将构造一个locked_ostream并将锁传递给那个
  5. locked_ostream::operator<<将委托打印namecout
  6. operator<<(std::ostream&, std::string)将打印名称
  7. cout感叹号和端线操纵器发生相同的委托
  8. locked_ostream临时被破坏,锁被释放

回答by Nickolas George

While I can't be sure this applies to every compiler / version of std libs but in the code-base I'm using std::cout::operator<<() it is already thread-safe.

虽然我不能确定这适用于 std 库的每个编译器/版本,但在代码库中我使用 std::cout::operator<<() 它已经是线程安全的。

I'm assuming that what you're really trying to do it stop std::cout from mixing string when concatenating with the operator<< multiple time per string, across multiple threads.

我假设你真正想要做的事情是在跨多个线程与 operator<< 多次连接时阻止 std::cout 混合字符串。

The reason strings get garbled is because there is a "External" race on the operator<< this can lead to things like this happening.

字符串出现乱码的原因是运算符<< 上存在“外部”竞争,这可能会导致此类情况的发生。

//Thread 1
std::cout << "the quick brown fox " << "jumped over the lazy dog " << std::endl;

//Thread 2
std::cout << "my mother washes" << " seashells by the sea shore" << std::endl;

//Could just as easily print like this or any other crazy order.
my mother washes the quick brown fox seashells by the sea sure \n
jumped of the lazy dog \n

If that's the case there is a much simpler answer than making your own thread safe cout or implementing a lock to use with cout.

如果是这种情况,那么有一个比创建自己的线程安全 cout 或实现与 cout 一起使用的锁更简单的答案。

Simply compose your string before you pass it to cout

只需在将字符串传递给 cout 之前组合它

For example.

例如。

//There are other ways, but stringstream uses << just like cout.. 
std::stringstream msg;
msg << "Error:" << Err_num << ", " << ErrorString( Err_num ) << "\n"; 
std::cout << msg.str();

This way your stings can't be garbled because they are already fully formed, plus its also a better practice to fully form your strings anyway before dispatching them.

这样,您的字符串就不会出现乱码,因为它们已经完全成型,而且在发送它们之前无论如何都要完全成型您的字符串也是一种更好的做法。

回答by Conchylicultor

I really like the trick from Nicolás given in this questionof creating a temporary object and putting the protection code on the destructor.

我真的很喜欢 Nicolás 在这个问题中给出的技巧,即创建一个临时对象并将保护代码放在析构函数上。

/** Thread safe cout class
  * Exemple of use:
  *    PrintThread{} << "Hello world!" << std::endl;
  */
class PrintThread: public std::ostringstream
{
public:
    PrintThread() = default;

    ~PrintThread()
    {
        std::lock_guard<std::mutex> guard(_mutexPrint);
        std::cout << this->str();
    }

private:
    static std::mutex _mutexPrint;
};

std::mutex PrintThread::_mutexPrint{};

You can then use it as a regular std::cout, from any thread:

然后,您可以std::cout从任何线程将其用作常规:

PrintThread{} << "my_val=" << val << std::endl;

The object collect data as a regular ostringstream. As soon the coma is reached, the object is destroyed and flush all collected information.

该对象定期收集数据ostringstream。一旦达到昏迷,对象就会被销毁并刷新所有收集的信息。

回答by lilezek

Since C++20, you can use std::osyncstreamwrapper:

因为C++20,您可以使用std::osyncstream包装器:

http://en.cppreference.com/w/cpp/io/basic_osyncstream

http://en.cppreference.com/w/cpp/io/basic_osyncstream

{
  std::osyncstream bout(std::cout); // synchronized wrapper for std::cout
  bout << "Hello, ";
  bout << "World!";
  bout << std::endl; // flush is noted, but not yet performed
  bout << "and more!\n";
} // characters are transferred and std::cout is flushed

It provides the guarantee that all output made to the same final destination buffer (std::cout in the examples above) will be free of data races and will not be interleaved or garbled in any way, as long as every write to the that final destination buffer is made through (possibly different) instances of std::basic_osyncstream.

它保证所有输出到同一个最终目标缓冲区(上面例子中的 std::cout )将没有数据竞争,并且不会以任何方式交错或乱码,只要每次写入该最终目标缓冲区目标缓冲区是通过 std::basic_osyncstream 的(可能不同的)实例制作的。

回答by Ulrich Eckhardt

A feasible solution uses a line-buffer for each thread. You might get interleaved lines, but not interleaved characters. If you attach that to thread-local storage, you also avoid lock contention issues. Then, when a line is full (or on flush, if you want), you write it to stdout. This last operation of course has to use a lock. You stuff all this into a streambuffer, which you put between std::cout and it's original streambuffer.

一个可行的解决方案为每个线程使用一个行缓冲区。您可能会得到交错的行,但不会得到交错的字符。如果将其附加到线程本地存储,还可以避免锁争用问题。然后,当一行已满(或刷新时,如果需要),将其写入标准输出。这最后一个操作当然必须使用锁。你把所有这些都塞进一个流缓冲区,你把它放在 std::cout 和它的原始流缓冲区之间。

The problem this doesn't solve is things like format flags (e.g. hex/dec/oct for numbers), which can sometimes percolate between threads, because they are attached to the stream. It's nothing bad, assuming you're only logging and not using it for important data. It helps to just not format things specially. If you need hex output for certain numbers, try this:

这没有解决的问题是格式标志(例如数字的十六进制/十进制/八进制)之类的问题,它们有时会在线程之间渗透,因为它们附加到流中。这没什么不好,假设您只是在记录而不是将其用于重要数据。它有助于不特别格式化东西。如果您需要某些数字的十六进制输出,请尝试以下操作:

template<typename integer_type>
std::string hex(integer_type v)
{
    /* Notes:
    1. using showbase would still not show the 0x for a zero
    2. using (v + 0) converts an unsigned char to a type
       that is recognized as integer instead of as character */
    std::stringstream s;
    s << "0x" << std::setfill('0') << std::hex
        << std::setw(2 * sizeof v) << (v + 0);
    return s.str();
}

Similar approaches work for other formats as well.

类似的方法也适用于其他格式。

回答by Germinx

For fast debugging c++11 applications and avoid interleaved output I just write small functions like these:

为了快速调试 c++11 应用程序并避免交错输出,我只编写如下小函数:

...
#include <mutex>
...
mutex m_screen;
...
void msg(char const * const message);
...
void msg(char const * const message)
{
  m_screen.lock();
  cout << message << endl;
  m_screen.unlock();
}

I use these types of functions for outputs and if numeric values are needed I just use something like this:

我将这些类型的函数用于输出,如果需要数值,我只使用这样的东西:

void msgInt(char const * const message, int const &value);
...
void msgInt(char const * const message, int const &value)
{
  m_screen.lock();
  cout << message << " = " << value << endl;
  m_screen.unlock();
}

This is easy and works fine to me, but I don't really know if it is technically correct. So I would be glad to hear your opinions.

这很简单,对我来说效果很好,但我真的不知道它在技术上是否正确。所以我很高兴听到你的意见。



Well, I didn't read this:

好吧,我没有读到这个:

I don't want to search for each occurrence of std::cout and add a line of locking code.

我不想搜索每次出现的 std::cout 并添加一行锁定代码。

I'm sorry. However I hope it helps somebody.

抱歉。不过我希望它可以帮助某人。

回答by eloiluiz

I know its an old question, but it helped me a lot with my problem. I created an utility class based on this post answers and I'd like to share my result.

我知道这是一个老问题,但它对我的问题有很大帮助。我根据这篇文章的答案创建了一个实用程序类,我想分享我的结果。

Considering we use C++11 or latter C++ versions, this class provides print and println functions to compose strings before calling the standard output stream and avoid concurrency problems. These are variadic functions which use templates to print different data types.

考虑到我们使用的是 C++11 或更高版本的 C++,这个类提供了 print 和 println 函数来在调用标准输出流之前组合字符串,避免并发问题。这些是使用模板打印不同数据类型的可变参数函数。

You can check its use in a producer-consumer problem on my github: https://github.com/eloiluiz/threadsBar

您可以在我的 github 上检查它在生产者-消费者问题中的使用:https: //github.com/eloiluiz/threadsBar

So, here is my code:

所以,这是我的代码:

class Console {
private:
    Console() = default;

    inline static void innerPrint(std::ostream &stream) {}

    template<typename Head, typename... Tail>
    inline static void innerPrint(std::ostream &stream, Head const head, Tail const ...tail) {
        stream << head;
        innerPrint(stream, tail...);
    }

public:
    template<typename Head, typename... Tail>
    inline static void print(Head const head, Tail const ...tail) {
        // Create a stream buffer
        std::stringbuf buffer;
        std::ostream stream(&buffer);
        // Feed input parameters to the stream object
        innerPrint(stream, head, tail...);
        // Print into console and flush
        std::cout << buffer.str();
    }

    template<typename Head, typename... Tail>
    inline static void println(Head const head, Tail const ...tail) {
        print(head, tail..., "\n");
    }
};

回答by cantordust

Along the lines of the answer suggested by Conchylicultor, but without inheriting from std::ostringstream:

沿着 Conchylicultor 建议的答案,但没有继承自std::ostringstream



EDIT: Fixed return type for the overloaded operator and added overload for std::endl.

编辑:修复了重载运算符的返回类型并为std::endl.



EDIT 1: I have extended this into a simple header-only libraryfor logging / debugging multi-threaded programs.

编辑 1:我已将其扩展为一个简单的仅标头库,用于记录/调试多线程程序。



#include <iostream>
#include <mutex>
#include <thread>
#include <vector>    
#include <chrono>

static std::mutex mtx_cout;

// Asynchronous output
struct acout
{
        std::unique_lock<std::mutex> lk;
        acout()
            :
              lk(std::unique_lock<std::mutex>(mtx_cout))
        {

        }

        template<typename T>
        acout& operator<<(const T& _t)
        {
            std::cout << _t;
            return *this;
        }

        acout& operator<<(std::ostream& (*fp)(std::ostream&))
        {
            std::cout << fp;
            return *this;
        }
};

int main(void)
{


    std::vector<std::thread> workers_cout;
    std::vector<std::thread> workers_acout;

    size_t worker(0);
    size_t threads(5);


    std::cout << "With std::cout:" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_cout.emplace_back([&]
        {
            std::cout << "\tThis is worker " << ++worker << " in thread "
                      << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_cout)
    {
        w.join();
    }

    worker = 0;

    std::this_thread::sleep_for(std::chrono::seconds(2));

    std::cout << "\nWith acout():" << std::endl;

    for (size_t i = 0; i < threads; ++i)
    {
        workers_acout.emplace_back([&]
        {
            acout() << "\tThis is worker " << ++worker << " in thread "
                    << std::this_thread::get_id() << std::endl;
        });
    }
    for (auto& w : workers_acout)
    {
        w.join();
    }

    return 0;
}

Output:

输出:

With std::cout:
        This is worker 1 in thread 139911511856896
        This is worker  This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911486678784
2 in thread     This is worker 5 in thread 139911503464192139911478286080


With acout():
        This is worker 1 in thread 139911478286080
        This is worker 2 in thread 139911486678784
        This is worker 3 in thread 139911495071488
        This is worker 4 in thread 139911503464192
        This is worker 5 in thread 139911511856896

回答by samvel1024

In addition to synchronisation this solution provides information about the thread from which the log was written.

除了同步之外,此解决方案还提供有关写入日志的线程的信息。

DISCLAIMER: It's quite a naive way of syncing the logs, however it might be applicable for some small use cases for debugging.

免责声明:这是同步日志的一种非常幼稚的方式,但是它可能适用于一些小的调试用例。

thread_local int thread_id = -1;
std::atomic<int> thread_count;

struct CurrentThread {

  static void init() {
    if (thread_id == -1) {
      thread_id = thread_count++;
    }
  }

  friend std::ostream &operator<<(std::ostream &os, const CurrentThread &t) {
    os << "[Thread-" << thread_id << "] - ";
    return os;
  }
};

CurrentThread current_thread;
std::mutex io_lock;
#ifdef DEBUG
#define LOG(x) {CurrentThread::init(); std::unique_lock<std::mutex> lk(io_lock); cout << current_thread; x << endl;}
#else
#define LOG(x)
#endif

This can be used like this.

这可以像这样使用。

LOG(cout << "Waiting for some event");

And it will give log output

它会给出日志输出

[Thread-1] - Entering critical section 
[Thread-2] - Waiting on mutex
[Thread-1] - Leaving critical section, unlocking the mutex