【问题标题】:Is qDebug() thread-safe?qDebug() 线程安全吗?
【发布时间】:2014-04-26 23:03:18
【问题描述】:

qDebug() 线程安全吗?我所说的线程安全不仅仅是指不崩溃,而且如果我从不同的线程调用qDebug(),输出是否可能会混淆?我用这段代码对其进行了测试,但似乎并非如此,但是,我在他们谈论这个的文档中找不到任何地方。

这是我的测试代码:

#include <QtConcurrent>
#include <QApplication>
void print_a() {
    for (int ii = 0; ii < 10000; ii++) {
        qDebug("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
    }
}
void print_b()
{
    for (int ii = 0; ii < 10000; ii++) {
        qDebug("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    }
}
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QtConcurrent::run(print_a);
    QtConcurrent::run(print_b);
    return a.exec();
}

在任何地方没有'a'和'b'混合在同一行,但我仍然不确定它是否 100% 线程安全...

【问题讨论】:

标签: c++ multithreading qt qdebug


【解决方案1】:

以下是我的答案和cmets:

  1. 如果 qDebug() 的文档没有提到它是否是线程安全的,我们应该假设它不是。答案可能取决于平台:如何在系统级别(Linux、Windows 等)实现 qDebug()。

  2. 我认为您不是在问一个更广泛的线程安全问题,而是像这样更具体的问题:“在多线程应用程序中使用 qDebug() 会导致交错的输出行吗?”答案是“是的,偶尔”。正如上面@dmcontador 产生的结果所证明的那样。当要打印的字符串变长时,概率会增加,正如上面@quetzalcoatl 所解释的那样。

  3. 答案并不取决于你是使用 qDebug("...") 还是 qDebug()

  4. 使用您的原始示例代码生成交错输出行对我来说并不容易。所以我创建了一个新示例,如下所示:

    #include <QCoreApplication>
    #include <QtConcurrent>
    
    #define MAX_ITERS 10
    #define MAX_LEN   10000
    
    void print_a()
    {
        QString a(MAX_LEN, 'a');
    
        for(int i = 0; i < MAX_ITERS; ++i) {
            qDebug().noquote() << a;
        }
    }
    
    void print_b()
    {
        QString b(MAX_LEN, 'b');
    
        for(int i = 0; i < MAX_ITERS; ++i) {
            qDebug().noquote() << b;
        }
    }
    
    int main(int argc, char * argv[])
    {
        QCoreApplication a(argc, argv);
        QtConcurrent::run(print_a);
        QtConcurrent::run(print_b);
        return 0;
    }
    

增加 MAX_LEN 时概率会增加。

  1. 后续问题是:“如何使用 qDebug() 生成非交错输出行?”一种解决方案是在每个 qDebug() 行上使用 QMutex。请注意,我没有尝试过这种不实用的解决方案。

【讨论】:

  • qDebug 和 QDebug 本身线程安全的。您所看到的“交错输出”源于 stderr(Qt 在大多数配置中默认记录到的)没有缓冲的事实。如果你有这么长的行,我建议改用不同的日志接收器(比如 Linux 上的 syslogd)。您可以通过将 qDebug() 替换为 fprintf(stderr, ...) 调用来轻松检查这一点。
【解决方案2】:

文档说If a function is not marked as thread-safe or reentrant, it should not be used from different threads。如果是qDebug(),它会说:Note: This function is thread-safe

此答案已更新...文档之前并未声明该函数是线程安全的。

【讨论】:

  • 等等,我实际上只是想了更多,线程安全就是使用同一个对象,对吧?但是qDebug() &lt;&lt; "foo"; 构造了一个新的QDebug 对象,所以我们将讨论重入,实际上,不是线程安全。抱歉,我之前没有提到它,但我现在想通了。
  • 是的,但 QRectQPoint 也不是。我专门发布了一个关于重入问题的新问题,你可以在stackoverflow.com/questions/22535094/…看到它
【解决方案3】:

恐怕它不是线程安全的。另外,我尝试了您的代码并且输出不一。

aaaaaaaaaaaabbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbabbbbbbbbbbbbbbbbbb

qDebug() &lt;&lt; "..." 我也有同样的运气

使用 mingw48_32 编译器在 Qt5.2.1 中测试。

【讨论】:

  • 赞成,因为我可以用 Qt 5.3.1 (MinGW 4.8.2 32bit) 确认这一点
【解决方案4】:

其实QtDebug相关的函数(如qDebug()、qWarning()等)所做的就是调用消息处理器,可以通过调用qInstallMessageHandler()来设置。所以这取决于你 - 这个消息处理程序是否是线程安全的。有一个默认实现,它只是将消息打印到 stderr,它不会阻止来自不同线程的混合输出,因此,如果您希望始终为您的调试消息、警告和错误提供非混合的逐行输出线程,您应该安装自己的带有某种锁定(例如 QMutex)的处理程序,如下所示:

QMutex messageMutex;

void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QMutexLocker locker(&messageMutex);

    [print msg and context somewhere]
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageHandler);

    QApplication app(argc, argv);

    [...]
}

注意:正如 Kuba Ober 正确指出的那样,这应该谨慎使用(不过,就像任何一般的锁定一样)。例如,如果在使用相同的 I/O 库输出调试消息时从 I/O 库内部调用 QtDebug 函数,则可能会出现死锁(例如,当 QtDebug 消息处理程序在持有锁的同时调用 I/O 时)在非递归互斥体上,然后底层 I/O 机器调用一些回调函数,然后这个回调函数调用 QtDebug 函数,该函数再次调用相同的处理程序。

【讨论】:

  • 需要注意的是,这可能是个糟糕的想法。为什么?因为如果您最终在持有其他互斥锁的同时调用消息处理程序,您可能会遇到死锁,因为无法保证会按照防止死锁所需的顺序获取互斥锁。例如,如果在代码中的其他任何地方使用标准输出,C 库可能拥有自己的互斥锁,等等。需要非常小心以确保此代码不会死锁。这是可能的,但不要认为你可以这样做并且它会“正常工作”。
  • 如果在使用相同的 I/O 库打印调试消息时从某些 I/O 库内部调用 QDebug 函数,您可能会出现死锁,这是真的。所以这个应该谨慎使用。我在答案中添加了相应的注释。
【解决方案5】:

我发现了这样的东西:http://www.qtcentre.org/threads/28879-redirecting-qDebug-to-file-threading-question

引用:

要回答 qdebug 是否是线程安全的问题: QDebug 使用 QTextstream。 QTextStream 不是线程安全的。 文档对此并不清楚,但是如果您查看 qdebug 或 qtextstream 的源代码,您会发现代码中根本没有互斥锁。

【讨论】:

  • 不过,这个答案来自 2006 年。考虑到我的测试代码在没有混合输出的情况下运行,事情可能已经改变。我可能得看看源代码。
  • 小心 - 输出流可能会被缓冲。缓冲区可能是内部同步的(而且经常是!),但整个机制不一定是。尝试对非常大的字符串执行相同的操作,以便缓冲区需要增长或分块字符串并等待。我不知道任何缓冲区可能有多大,但是您当前的测试字符串很短。
  • 这大约是QDebug,您通过qDebug() 而不是qDebug(..) 获得。所以使用qDebug() &lt;&lt; "aaaaaaaaaaaaaaa"是不安全的。没有告诉我们任何关于 C 风格的版本
  • @BeniBela 实际上qDebug() 是一个创建全新QDebug 实例的函数。因此线程不会访问相同的数据,除非涉及全局变量。
【解决方案6】:

实际上qDebug( ..text.. ) 是线程安全的(至少在使用 gcc 编译的情况下)。

如果查看 qt(4) 源文件 qglobal.cppqDebug 调用 qt_message_output,后者调用 fprintf(stderr, ...),这在 glibc 中是线程安全的

qDebug() &lt;&lt; .. 是另一个故事)

【讨论】:

  • 为什么qDebug() 会是另一个故事?不是也叫fprintf吗?
  • 我也发布了question about fprintf for MSVCRT,因为我在 Windows 上使用 Qt。
  • 看来fprintf() 对于 MSVCRT 也是线程安全的,所以使用 Qt 和 Visual Studio 工具链,qDebug() 应该仍然是线程安全的。
  • 不是正确的。看到这个答案:stackoverflow.com/a/23517726/1202500
  • @mozzbozz: 也许它在 qt5 中被改变了?顺便说一句,#include &lt;QtConcurrentRun&gt; 在源代码中是正确的。 QtConcurrent 在 qt4 中不存在
【解决方案7】:

两者

qDebug("xx")

还有

qDebug() << "xx"

qInfo、qWarning、qCritical 以及 qCDebug、qCInfo、qCWarning、qCritical 等分类版本可以安全地从不同线程同时使用。

但是,您必须确保日志接收器也可以原子地处理大数据。这是混乱的来源,因为 stderr 显然打破了太长的行。您可以通过在示例中将 qDebug() 替换为 fprintf(stderr) 来轻松验证这一点:它对我显示完全相同的行为。

您可以尝试其他日志接收器,例如 journald。无论如何,他们也可能对最大长度施加限制。一般来说,我建议保持日志消息的最大长度合理。

【讨论】:

    猜你喜欢
    • 2020-04-15
    • 2011-07-04
    • 2012-11-30
    • 2010-12-30
    • 2013-03-12
    • 2021-08-03
    • 2010-12-27
    • 2018-06-04
    • 2011-09-18
    相关资源
    最近更新 更多