非中断I / O的最佳实践

时间:2012-04-16 17:49:36

标签: c++ multithreading logging qt4 qtconcurrent

我正在使用一个小应用程序,需要生成一个非常详细的日志。我已经实现了一个简单的单例Logger类,看起来像这样

#ifndef LOGGER_H
#define LOGGER_H

#include <QObject>
#include <QMutex>
#include <QFile>

class Logger : public QObject
{
    Q_OBJECT
public:
    static Logger* sharedInstance()
    {
        static QMutex mutex;

        if (!m_sharedInstance)
        {
            mutex.lock();

            if(!m_sharedInstance)
                m_sharedInstance = new Logger;

            mutex.unlock();
        }

        return m_sharedInstance;
    }

    static void dropInstance()
    {
        static QMutex mutex;
        mutex.lock();
        delete m_sharedInstance;
        m_sharedInstance = 0;
        mutex.unlock();
    }

    void setLogFilePathAndOpen(QString path);
    void logMessage(QString message);
    void logMessageWorker(QString message);
    void closeLog();

private:
    Logger() {}

    Logger(const Logger &);
    Logger& operator=(const Logger &);

    static Logger *m_sharedInstance;

    QFile m_logFile;

signals:

public slots:

};

#endif // LOGGER_H

#include <QDateTime>
#include <QtConcurrentRun>

#include "logger.h"

Logger* Logger::m_sharedInstance = 0;

void Logger::setLogFilePathAndOpen(QString path)
{
    m_logFile.setFileName(path);
    m_logFile.open(QIODevice::Append | QIODevice::Text);
}

void Logger::logMessage(QString message)
{
    //TODO calling QtConcurrent causes about a 22% drop in performance. Look at other ways to do this.
    QtConcurrent::run(this, &Logger::logMessageWorker, message);
}

void Logger::logMessageWorker(QString message)
{
    QTextStream out(&m_logFile);
    out << tr("%1: %2\n").arg(QDateTime::currentDateTime().toString()).arg(message);
}

void Logger::closeLog()
{
    m_logFile.close();
}

现在我对Qt和C ++有些新意,也许这都是错的,所以对我来说很容易:)。现在我的性能下降了22%,而非记录,使用这种方法,这是我能够管理的最好的方法。我认为性能打击来自于创建QtConcurrent线程。

我想我的问题是,这是最好的方法,还是有更好的方法来实现这一点,将会看到更接近完全没有记录的性能。我知道无论应用程序的日志记录速度会有多慢,但我尽量尽量减少这种情况。

3 个答案:

答案 0 :(得分:3)

由于您愿意忍受异步日志记录,因此您通常希望在单个线程中完成日志记录,并且具有向其提供数据的线程安全队列。有很多线程安全的队列,比如我在previous answer中发布的队列。您(显然)想要使用Qt原语重写它,但我相信它的相似性足以使重写主要更改您使用的名称。

从那里开始,日志变得更加简单。日志记录线程只是从队列中检索数据,将其写入日志并重复。由于日志记录线程是唯一直接触及日志的线程,因此它根本不需要执行任何锁定 - 它本质上是单线程代码,并且所有锁定都在队列中处理。

答案 1 :(得分:1)

有两种方法可以解决这个问题:

  1. 重用现有的日志记录框架,或至少窃取其想法。
  2. 停止记录这么多(但你仍然应该做1。)

  3. 最佳做法?

    通常,您应该将日志排入队列,该队列由工作线程排队。有许多可能的实现(无锁队列,池队列,等等),但没关系,只需选择一个,你已经获得了非常好的性能。

    当然,简单地重用现有框架会好得多。你看过Boost.Log还是Pantheios?


    要注意的另一件事是你不应该记录这么多。日志在应用程序中很少见。

    • 对于调试运行,您可以assert自由地获得完整的核心转储,以防出现问题,更有帮助
    • 对于发布运行,您可以使用条件日志记录仅在出现异常时登录

    条件记录技巧可能有点违反直觉。我们的想法是使用对象的析构函数来实际记录,并且只有当std::uncaught_exception()返回true时才会这样做。当然这意味着这个日志记录应该是无异常的,所以除非你对你的编程技巧非常有信心,否则我建议你预先格式化这个消息。

答案 2 :(得分:0)

我正在使用qDebug,qWarning等进行日志记录。然后,以这种方式将事件消息重定向到日志文件:

...
qInstallMsgHandler(messageHandler);
...

#define LOG_FILE "path/to/file.log"

void messageHandler(QtMsgType type, const char *msg)
{
#define NOW QDateTime::currentDateTime().toString("MM:dd: hh:mm:ss")
  QString out;
  switch (type) {
  case QtDebugMsg:    out = QString("%1: %2\n").arg(NOW).arg(msg); break;
  case QtWarningMsg:  out = QString("%1: warning: %2\n").arg(NOW).arg(msg); break;
  case QtCriticalMsg: out = QString("%1: critical: %2\n").arg(NOW).arg(msg); break;
  case QtFatalMsg:    out = QString("%1: fatal: %2\n").arg(NOW).arg(msg); break;
  default: return;
  }

  QFile file(LOG_FILE);
  if (file.open(QIODevice::WriteOnly | QIODevice::Append))
    QTextStream(&file) << out;
#undef NOW
}

这种方法是线程安全的,虽然它不是最有效的方法,因为文件IO没有被缓存。