【问题标题】:Asynchronous thread-safe logging in C++C++ 中的异步线程安全日志记录
【发布时间】:2010-05-28 15:24:08
【问题描述】:

如果可能的话,我正在寻找一种在我的 C++ 项目中进行异步和线程安全日志记录的方法。我目前正在为任务使用cerrclog,但由于它们是同步的,因此每次记录某些内容时执行都会暂停。这是一个相对图形繁重的应用程序,所以这种事情很烦人。

新的记录器应该使用异步 I/O 来消除这些暂停。线程安全也是可取的,因为我打算很快添加一些基本的多线程。

我考虑过一种每个线程一个文件的方法,但这似乎会使管理日志成为一场噩梦。有什么建议吗?

【问题讨论】:

    标签: c++ logging asynchronous thread-safety


    【解决方案1】:

    我注意到这个 1 岁以上的帖子。也许我写的异步记录器可能会感兴趣。

    http://www.codeproject.com/KB/library/g2log.aspx

    G2log 使用受保护的消息队列将日志条目转发到慢速磁盘访问的后台工作程序。

    我已经尝试过使用无锁队列来增加 LOG 调用的平均时间,但减少了最坏情况的时间,但是我现在正在使用受保护的队列,因为它是跨平台的。它在 Windows/Visual Studio 2010 和 Ubuntu 11.10/gcc4.6 上进行了测试。

    它已作为公共领域发布,因此您可以随意使用它而无需附加任何条件。

    【讨论】:

    • 如果日志记录不能导致任何阻塞(例如,使用打印调试竞争条件......嘿,它有效而且很简单!),我通常做的事情是使用存储令牌的线程本地消息队列带有时间戳(在这种情况下可能会更好地直接使用异步 IO 输出)。如果您需要数据,您只需稍后对输出进行排序,这对于一般日志记录来说相当麻烦,但它的同步量最低:nil。
    • 重要的当然不仅是线程安全和最小阻塞,还要确保在软件崩溃(分段错误、浮点错误等)时日志条目在文件中)。 G2log 通过使用信号处理程序提供,在“致命信号”的情况下,它会在信号继续之前将排队的条目刷新到文件中(并中止程序)
    • @KjellHedström - 按照此链接中的步骤stackoverflow.com/help/user-merge 合并您的帐户。
    • 谢谢克里斯。我删除了另一个已过时的“1 分”帐户。
    • 现在有几个异步的、线程安全的、日志库。有几个比 g2log 快。 g2log 的继任者称为 g3log github.com/KjellKod/g3log,其他社区接收器位于 github.com/KjellKod/g3sinks
    【解决方案2】:

    这是非常可能和实用的。我怎么知道?我在上一份工作中就是这么写的。不幸的是(对我们来说),他们现在拥有代码。 :-) 可悲的是,他们甚至不使用它。

    我打算在不久的将来编写一个开源版本。同时,我可以给你一些提示。

    1. I/O 操纵器实际上只是函数名称。您可以为自己的日志记录类实现它们,以便您的记录器与 cout/cin 兼容。
    2. 您的操纵器函数可以标记操作并将它们存储到队列中。
    3. 线程可能会在该队列中被阻塞,等待日志块飞过。然后它处理字符串操作并生成实际的日志。

    这本质上是线程兼容的,因为您使用的是队列。但是,您仍然希望在写入队列时放置一些类似互斥锁的保护,以便给定日志

    玩得开心!

    【讨论】:

    • 互斥锁不是意味着如果一个线程已经在尝试写入,它就必须旋转直到它被解锁?这有什么帮助?
    • 我当然不会延迟将对象转换为流。将它放在另一个线程中意味着必须对其进行同步......这可能会很痛苦。
    • @Electro - 不是旋转,而是阻塞。但是,如果您不进行字符串处理,那么加载队列的时间间隔非常短。字符串处理由日志线程完成。 @Matthieu,在加载队列时,实时处理字符串比任何互斥体阻塞更痛苦。
    • 我需要立即传递日志消息以进行异步写入,并且我的线程的任何等待都可能再次导致烦人的暂停。
    • 异步处理字符串会导致所有记录对象的线程安全问题,正确性先于性能,这里的问题不是队列阻塞而是IO缓慢。
    【解决方案3】:

    我认为正确的方法不是每个线程一个文件,而是每个文件一个线程。如果您系统中的任何一个文件(或一般的资源)只能由一个线程访问,那么线程安全编程就会变得容易得多。

    那么为什么不让 Logger 成为一个专用线程(或多个线程,每个文件一个,如果您在不同的文件中记录不同的内容),并且在所有其他线程中,写入日志会将消息放在输入队列中适当的 Logger 线程,它会在写完上一条消息后到达它。只需要一个互斥锁来保护队列在 ​​Logger 读取事件时不添加事件,以及一个 condvar 让 Logger 在其队列为空时等待。

    【讨论】:

    • 您可以不使用带有无锁队列的互斥锁。但是,条件变量却大不相同。根据费用的不同,您可能更喜欢旋转而不是睡觉并稍后醒来。这还取决于您是否有空闲核心:)
    • 如果我们的系统中有一些繁忙的等待,看门狗将杀死整个应用程序:)
    【解决方案4】:

    您是否考虑过使用日志库。

    有几个可用的,我最近发现了Pantheios,它看起来真的很不可思议。

    它更像是一个前端记录器,您可以自定义使用哪个系统。例如,它可以与ACElog4cxx 交互,并且看起来非常易于使用和配置。主要优点是它使用类型安全的运算符,这总是很棒。

    如果您只想要一个准系统日志库:

    • 王牌
    • log4c*
    • Boost.Log

    随便选一个:)

    我应该注意到,可以在 C++ 中实现无锁队列,并且它们非常适合记录。

    【讨论】:

    • 嗯,我想我不想引入任何这些。前两者看起来有点矫枉过正,而后者在评测中存在很多性能问题,这也是他们没有将其正确添加到 Boost 集合中的原因之一。
    【解决方案5】:

    我遇到了同样的问题,我相信我已经找到了完美的解决方案。我向您介绍一个名为 loguru 的单头库:https://github.com/emilk/loguru

    它易于使用、可移植、可配置、基于宏,并且默认情况下不会 #include 任何东西(对于那些甜蜜、甜蜜的编译时间)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多