Хорошо ли создавать отдельный поток для логгера?

Я пишу многопоточное приложение на Qt (несколько потоков со своими собственными циклами событий). При входе в систему я хочу, чтобы регистратор включил в журнал идентификатор потока (они имеют значимые имена). Регистратор Qt по умолчанию кажется неспособным сделать это.
Итак, у меня есть три варианта:

  1. каждый поток ведет логи само по себе (это касается мьютексов, так что, вероятно, это худший подход, но я не уверен)
  2. Там есть специальный поток логгер и другие темы непосредственно публиковать события в него (вероятно, быстрее, чем 3.)
  3. То же, что и 2., но сообщение отправляется через система сигналов / слотов (на самом деле, это также приведет к публикации события).

Какой из них лучше, и каковы лучшие практики, делающие это в целом?


Некоторые вещи, чтобы уточнить после вопросов в комментариях:

  • QThread имеет стандартный метод postEvent()это потокобезопасно.

Таким образом, возникает вопрос: нужно ли потоку журнала выполнять достаточно работы для каждого события, чтобы оправдать затраты на маршалинг данных события через какую-то очередь?

  • В этом суть вопроса. Я знаю, что лучшим ответом будет «Измерить!», Но в настоящее время приложение находится на ранней стадии разработки, и измерять особо нечего. Также всегда хорошо выбрать правильный дизайн с самого начала.
  • В моем случае потоки, вероятно, хорошая идея: это медиаплеер, так что есть поток GUI, поток воспроизведения, поток DB / Media библиотеки, пул сетевых потоков … Другими словами, целый Zoo потоков.

9

Решение

Это общие замечания, так как у меня нет опыта работы с Qt. Что касается стоимости очередей, в целом: ввод-вывод обычно позволяет другим затратам времени выполнения бледнеть, поэтому это не должно иметь значения.

Свойства выделенного потока регистрации:

  • Хорошо: минимальное влияние на поведение программы во время выполнения.
  • Хорошо: гарантированные сообщения в одном журнале (не смешанный вывод из нескольких потоков).
  • Плохо: значительные усилия по внедрению.
  • Плохо: время начала и фактического ведения журнала разделено (вот и весь смысл!). Вы можете увидеть результаты регистрации не там, где вы ожидаете.
  • Плохо: завершающая программа может проглотить последние и самые важные сообщения журнала (точка Андреаса), поэтому вы можете захотеть добавить функцию синхронного журнала (это крайний пункт выше).

(Дис) преимущества ведения журнала от каждого потока прямо противоположны вышеизложенному. Никакие два потока не могут войти в систему одновременно (либо потому, что такие функции, как printf() неявно заблокировать FILEили потому что вы синхронизируете функцию журнала явно); это делает все потоки, которые хотят войти, блокировать, пока текущий поток не будет сделан. Если ведение журнала выполняется для целей отладки, может потребоваться запись без буферизации (чтобы данные не были потеряны в случае последующего сбоя), что усугубляет влияние времени выполнения.

Насколько это плохо, зависит от характера приложения, а также от механизма ведения журнала и объема данных.

8

Другие решения

Я реализовал механизмы ведения журналов для приложений 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);
}
2

Я не совсем понимаю, почему каждый поток, делающий запись сам по себе, должен использовать явный мьютекс.

Если вы входите в файл на диске, то каждый поток может записывать в свой файл. Вы можете назвать файлы с общим префиксом:

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 выполнил бы как минимум два распределения.

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector