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

新手上路,请多包涵

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

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

有更好的做法吗?

原文由 xmllmx 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 912
2 个回答

注意:此答案是 C++20 之前的版本,因此它不使用 std::osyncstream 及其单独的缓冲,而是使用锁。

我想您可以实现自己的类,该类包装 cout 并将互斥锁与它相关联。新类的 operator << 会做三件事:

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

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

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

我们将这两个类称为 synchronized_ostreamlocked_ostream 。如果 sync_cout 是 --- 的一个实例,它包装了 synchronized_ostream std::cout ,那么序列

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

将导致以下操作:

  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<< 将打印 name --- 委托给 cout
  6. operator<<(std::ostream&, std::string) 将打印名称
  7. cout 的相同委托发生在感叹号和结束线操纵器上
  8. locked_ostream 临时被破坏,锁被释放

原文由 MvG 发布,翻译遵循 CC BY-SA 4.0 许可协议

我遇到了和你类似的问题。您可以使用以下类。这仅支持输出到 std::cout ,但是如果您需要通用的,请告诉我。在下面的代码中, 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替换 cout 的任何实例,但我一般不推荐这种做法。无论如何,从项目的开头开始对此类调试行使用特殊的输出符号要好得多。

我也喜欢这个解决方案: 1 。在我的解决方案中,所有线程都可以继续插入其相应的 thread_local stringstream 静态对象,然后仅在需要刷新时才锁定互斥锁,这在析构函数中触发。这有望通过缩短互斥锁的持有时间来提高效率。也许我可以包含一个类似于 sync_endl 解决方案中提到的机制 1

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

原文由 Eddie 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题