Я пишу многопоточное приложение на Qt (несколько потоков со своими собственными циклами событий). При входе в систему я хочу, чтобы регистратор включил в журнал идентификатор потока (они имеют значимые имена). Регистратор Qt по умолчанию кажется неспособным сделать это.
Итак, у меня есть три варианта:
Какой из них лучше, и каковы лучшие практики, делающие это в целом?
Некоторые вещи, чтобы уточнить после вопросов в комментариях:
postEvent()
это потокобезопасно.Таким образом, возникает вопрос: нужно ли потоку журнала выполнять достаточно работы для каждого события, чтобы оправдать затраты на маршалинг данных события через какую-то очередь?
Это общие замечания, так как у меня нет опыта работы с Qt. Что касается стоимости очередей, в целом: ввод-вывод обычно позволяет другим затратам времени выполнения бледнеть, поэтому это не должно иметь значения.
Свойства выделенного потока регистрации:
(Дис) преимущества ведения журнала от каждого потока прямо противоположны вышеизложенному. Никакие два потока не могут войти в систему одновременно (либо потому, что такие функции, как printf()
неявно заблокировать FILE
или потому что вы синхронизируете функцию журнала явно); это делает все потоки, которые хотят войти, блокировать, пока текущий поток не будет сделан. Если ведение журнала выполняется для целей отладки, может потребоваться запись без буферизации (чтобы данные не были потеряны в случае последующего сбоя), что усугубляет влияние времени выполнения.
Насколько это плохо, зависит от характера приложения, а также от механизма ведения журнала и объема данных.
Я реализовал механизмы ведения журналов для приложений Qt в хорошем чистом виде, используя Механизм Qt Event.
В приложении 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);
}
Я не совсем понимаю, почему каждый поток, делающий запись сам по себе, должен использовать явный мьютекс.
Если вы входите в файл на диске, то каждый поток может записывать в свой файл. Вы можете назвать файлы с общим префиксом:
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
выполнил бы как минимум два распределения.