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

问题描述 投票:25回答:9

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

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

有更好的做法吗?

c++ multithreading logging locking iostream
9个回答
17
投票

注意:这个答案是在C ++之前的20,因此它不会将std::osyncstream与其单独的缓冲一起使用,而是使用锁定代替。

我猜你可以实现你自己的类包装cout并将互斥量与它相关联。新课程的operator <<会做三件事:

  1. 为互斥锁创建一个锁,可能会阻塞其他线程
  2. 执行输出,即为包装流和传递的参数执行运算符<<
  3. 构造一个不同类的实例,将锁传递给它

这个不同的类将保持锁定和委托运算符<<到包装流。第二类的析构函数最终会破坏锁并释放互斥锁。

因此,只要所有输出都使用相同的互斥锁通过该对象,您将作为单个语句写入的任何输出(即作为单个<<调用序列)将以原子方式打印。

我们叫两个类synchronized_ostreamlocked_ostream。如果sync_cout是包裹synchronized_ostreamstd::cout的一个实例,那么序列

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

会导致以下操作:

  1. qazxsw poi将获得锁定
  2. synchronized_ostream::operator<<将把“你好”的印刷委托给synchronized_ostream::operator<<
  3. cout将打印“你好”,
  4. operator<<(std::ostream&, const char*)将构建一个synchronized_ostream::operator<<并将锁定传递给它
  5. locked_ostream将把locked_ostream::operator<<的印刷委托给name
  6. cout会打印出这个名字
  7. operator<<(std::ostream&, std::string)的同一个代表团发生在感叹号和终点操纵器上
  8. cout临时被破坏,锁被释放

25
投票

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

我假设你真正试图做的就是在与多个线程的每个字符串多次连接时,将std :: cout从混合字符串中停止。

字符串变得混乱的原因是因为操作符上存在“外部”竞争<<这可能导致这样的事情发生。

locked_ostream

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

在将它传递给cout之前,只需编写一下你的字符串

例如。

//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

这样你的叮咬就不会因为它们已经完全形成而出​​现乱码,而且在发送之前无论如何还要更好地完成你的弦乐。


11
投票

我非常喜欢Nicazás在//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 question

然后,您可以从任何线程将其用作常规/** 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{};

std::cout

该对象收集数据作为常规PrintThread{} << "my_val=" << val << std::endl; 。一旦达到昏迷,就会销毁对象并清除所有收集的信息。


8
投票

ostringstream,您可以使用C++20包装:

std::osyncstream

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

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


4
投票

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

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

{
  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

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


3
投票

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

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

我将这些类型的函数用于输出,如果需要数值,我只需使用以下内容:

...
#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();
}

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


好吧,我没看过这个:

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

对不起。但是我希望它对某人有所帮助。


1
投票

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

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

您可以在我的github上查看其在生产者 - 消费者问题中的用法: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(); }

所以,这是我的代码:

https://github.com/eloiluiz/threadsBar

1
投票

沿着Conchylicultor提出的答案,但没有继承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"); } };


编辑:修复了重载运算符的返回类型,并为std::ostringstream添加了重载。


编辑1:我已将其扩展为std::endl,用于记录/调试多线程程序。


simple header-only library

输出:

#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;
}

0
投票

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

免责声明:这是同步日志的一种天真的方式,但它可能适用于一些小的用例进行调试。

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

这可以这样使用。

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

它会给出日志输出

LOG(cout << "Waiting for some event");
© www.soinside.com 2019 - 2024. All rights reserved.