简体   繁体   English

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

[英]How to easily make std::cout thread-safe?

I have a multi-threaded application, which heavily uses std::cout for logging without any locking.我有一个多线程应用程序,它大量使用std::cout进行日志记录而没有任何锁定。 In such a case, how can I easily add lock mechanism to make std::cout thread-safe?在这种情况下,如何轻松添加锁定机制以使std::cout线程安全?

I don't want to search for each occurrence of std::cout and add a line of locking code.我不想搜索每次出现的std::cout并添加一行锁定代码。 That is too tedious.那太乏味了。

Any better practice?有什么更好的做法吗?

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.字符串出现乱码的原因是operator<<上存在“外部”竞争,这可能会导致此类情况的发生。

//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 shore \n
jumped over 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.这样,您的字符串就不会出现乱码,因为它们已经完全成型,而且在发送它们之前无论如何都要完全成型您的字符串也是一种更好的做法。

Note: This answer is pre-C++20 so it does not use std::osyncstream with its separate buffering, but uses a lock instead.注意:这个答案是 C++20 之前的,所以它不使用std::osyncstream及其单独的缓冲,而是使用锁代替。

I guess you could implement your own class which wraps cout and associates a mutex with it.我想您可以实现自己的类,该类包装cout并将互斥体与其关联。 The operator << of that new class would do three things:那个新类的operator <<会做三件事:

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

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, ie 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_ostream and locked_ostream .我们locked_ostream两个类称为synchronized_ostreamlocked_ostream If sync_cout is an instance of synchronized_ostream which wraps std::cout , then the sequence如果sync_cout是包装std::coutsynchronized_ostream的实例,则序列

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

would result in the following actions:将导致以下操作:

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

I really like the trick from Nicolás given in this question of 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 .该对象将数据作为常规ostringstream收集。 As soon the coma is reached, the object is destroyed and flush all collected information.一旦达到昏迷,对象就会被销毁并刷新所有收集的信息。

Since C++20 , you can use std::osyncstream wrapper: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 的(可能不同的)实例制作的。

Alternatively, you can use a temporary:或者,您可以使用临时:

std::osyncstream(std::cout) << "Hello, " << "World!" << '\n';

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.不过我希望它可以帮助某人。

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 (eg 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.类似的方法也适用于其他格式。

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 library for 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

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.考虑到我们使用的是 C++11 或更高版本的 C++,这个类提供了 print 和 println 函数来在调用标准输出流之前组合字符串,避免并发问题。 These are variadic functions which use templates to print different data types.这些是使用模板打印不同数据类型的可变参数函数。

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

This is how I manage thread safe operations on std::cout using a custom enum and macros:这就是我使用自定义枚举和宏在std::cout上管理线程安全操作的方式:

enum SynchronisedOutput { IO_Lock, IO_Unlock };

inline std::ostream & operator<<(std::ostream & os, SynchronisedOutput so) {
  static std::mutex mutex;

  if (IO_Lock == so) mutex.lock();
  else if (IO_Unlock == so)
    mutex.unlock();

  return os;
}

#define sync_os(Os) (Os) << IO_Lock
#define sync_cout sync_os(std::cout)
#define sync_endl '\n' << IO_Unlock

This allow me to write things like:这让我可以写这样的东西:

sync_cout << "Hello, " << name << '!' << sync_endl;

in threads without racing issues.在没有赛车问题的线程中。

I had a similar problem to yours.我遇到了和你类似的问题。 You can use the following class.您可以使用以下类。 This only supports outputting to std::cout , but if you need a general one let me know.这仅支持输出到std::cout ,但如果您需要通用的,请告诉我。 In the code below, tsprint creates an inline temporary object of the class ThreadSafePrinter .在下面的代码中, tsprint创建了一个ThreadSafePrinter类的内联临时对象。 If you want, you can change tsprint to cout if you have used cout instead of std::cout , so you won't have to replace any instances of cout but I do not recommend such a practice in general.如果需要,如果您使用cout而不是std::cout ,则可以将tsprint更改为cout ,这样您就不必替换任何cout实例,但我一般不推荐这种做法。 It is much better to use a special output symbol for such debug lines starting from the beginning of the project anyway.无论如何,从项目开始就为此类调试行使用特殊的输出符号要好得多。

class ThreadSafePrinter
{
    static mutex m;
    static thread_local stringstream ss;
public:
    ThreadSafePrinter() = default;
    ~ThreadSafePrinter()
    {
        lock_guard  lg(m);
        std::cout << ss.str();
        ss.clear();
    }

    template<typename T>
    ThreadSafePrinter& operator << (const T& c)
    {
        ss << c;
        return *this;
    }


    // this is the type of std::cout
    typedef std::basic_ostream<char, std::char_traits<char> > CoutType;

    // this is the function signature of std::endl
    typedef CoutType& (*StandardEndLine)(CoutType&);

    // define an operator<< to take in std::endl
    ThreadSafePrinter& operator<<(StandardEndLine manip)
    {
        manip(ss);
        return *this;
    }
};
mutex ThreadSafePrinter::m;
thread_local stringstream ThreadSafePrinter::ss;
#define tsprint ThreadSafePrinter()

void main()
{
    tsprint << "asd ";
    tsprint << "dfg";
}

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

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

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