我正在Qt中编写一个多线程应用程序(几个线程都有自己的事件循环)。记录时,我希望记录器在日志中包含一个线程ID(它们具有有意义的名称)。 Qt默认记录器似乎无法做到这一点。 所以我有三个选择:
哪一个更好,一般的最佳做法是什么?
在评论中提出问题后要澄清一些事项:
postEvent()
,它是线程安全的。所以问题就变成了,记录器线程是否需要为每个事件做足够的工作来证明在某种队列中编组事件数据的成本
答案 0 :(得分:8)
这些是一般性评论,因为我没有使用Qt的经验。关于排队的成本,一般来说:I / O通常会让其他运行时成本变得苍白,所以无所谓。
专用日志记录线程的属性:
直接从每个线程记录的(dis)优点与上述相反。没有两个线程可以同时记录(因为printf()
之类的函数隐式锁定FILE
,或者因为你明确地同步了日志函数);这使得所有想要记录阻塞的线程直到当前线程完成。如果为了调试目的而进行了日志记录,则可能希望记录无缓冲(以便在发生后续崩溃时数据不会丢失),这会加剧运行时的影响。
这有多糟糕取决于应用程序的性质以及日志记录机制和数据量。
答案 1 :(得分:2)
我使用Qt Event mechanism以干净的方式为Qt应用程序实现了日志记录机制。
在Qt应用程序中,有一个代表应用程序的QApplication实例。
您可以通过继承QEvent来创建自己的事件,然后发布它们并使用应用程序的QApplication对象处理它们。
例如,您可能拥有日志事件类
MyLogEvent : public QEvent
{
public:
MyLogEvent(QString threadId, QString logMessage) : QEvent(QEvent::User)
{ /* Store the ThreadID and log message, with accessor functions */};
}
您可以使用
从任何Qt线程发布事件MyLogEvent *event = new MyLogEvent(QString("Thread 1"), QString("Something Happened"));
QApplication::postEvent(mainWindow, event);
处理程序可以是主窗口对象(如果要记录到窗口),也可以是专用对象(如果要例如登录到文件。
在处理事件的对象中,重写QObject :: event以处理日志消息
bool MainWindow::event(QEvent *e)
{
if(e->type()==QEvent::User)
{
// This is a log event
MyLogEvent *logEvent = static_cast<MyLogEvent *>(e);
ui.textEdit->appendPlainText(logEvent->logMessage())
return true;
}
return QMainWindow::event(e);
}
答案 2 :(得分:1)
我不太明白为什么每个执行日志记录的线程都需要使用显式的互斥锁。
如果您正在登录磁盘文件,那么每个线程都可以记录到自己的文件中。您可以使用通用前缀命名文件:
QFile * logFile(QObject * parent = nullptr) {
auto baseName = QStringLiteral("MyApplication-");
auto threadName = QThread::currentThread()->objectName();
if (threadName.isEmpty())
return new QTemporaryFile(baseName);
else
return new QFile(baseName + threadName);
}
操作系统通过其文件系统互斥锁序列化访问。
如果您正在登录支持并发访问的数据库,例如选择了正确并发选项的sqlite,则数据库驱动程序将负责序列化访问。
如果您要登录到一个公共线程,那么事件队列中有一个互斥锁,您可以在postEvent
时自动序列化。
你是对的,使用信号插槽机制对你直接使用事件并没有太大的帮助。实际上,它可以保证执行更多的内存分配,因此您应该更喜欢自己发布一个事件,理想情况下是使用大小适合“大多数”日志消息的QVarLengthArray<char>
的事件。然后,使用单个malloc
调用分配此类事件:
// logger.h
struct MyLogEvent : QEvent {
constexpr static QEvent::Type theType() { return (QEvent::Type)(QEvent::User + 1); }
QVarLengthArray<char, 128> message;
MyLogEvent(const char * msg) : QEvent(theType()) {
message.append(msg, strlen(msg));
}
};
class Logger : public QObject {
...
public:
static void log(const char * msg) {
QCoreApplication::postEvent(instance(), new MyLogEvent(msg));
}
static Logger * instance(); // singleton, must be a thread safe method
};
// logger.cpp
...
Q_GLOBAL_STATIC(Logger, loggerInstance);
Logger * Logger::instance() {
// Thread-safe since QGlobalStatic is.
return loggerInstance;
}
如果您使用QByteArray
或QString
,则表达式new MyLogEvent
至少会执行两次分配。