Dans le développement d'applications avec Qt, l'écriture inetnsive de journaux (logs) sur le disque peut impacter significtaivement les performances, notamment en bloquant le thread principal (UI thread). Pour pallier ce problème, une solution efficace consiste à déporter les opérations d'écriture dans un thread dédié.
Cette approhce repose sur un modèle producteur-consommateur utilisant une file d'attente sécurisée, un verrou mutuel pour l'accès aux données et un sémaphore pour la synchronisation.
1. Définition de la classe LoggerThread
Le fichier d'en-tête définit une classe héritant de QThread. Nous utilisons une QStringList pour stocker les messages en attente de traitement.
#ifndef LOGGERTHREAD_H
#define LOGGERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QSemaphore>
#include <QStringList>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
class LoggerThread : public QThread
{
Q_OBJECT
public:
explicit LoggerThread(QObject *parent = nullptr);
~LoggerThread();
// Méthode appelée par les autres threads pour enregistrer un message
void postMessage(const QString &text);
protected:
void run() override;
private:
QMutex m_bufferLock; // Protège l'accès à la liste de messages
QSemaphore m_signalInput; // Gère l'attente du thread
QStringList m_logsQueue; // File d'attente des messages
bool m_isRunning;
};
#endif // LOGGERTHREAD_H
2. Implémentation de la logique de journalisation
Dans l'implémentation, la méthode postMessage ajoute des données au tampon, tandis que la méthode run consomme ces données en boucle fermée, en restant bloquée tant qu'aucun nouveau message n'est disponible.
#include "loggerthread.h"
LoggerThread::LoggerThread(QObject *parent)
: QThread(parent), m_isRunning(true)
{
}
LoggerThread::~LoggerThread()
{
m_isRunning = false;
m_signalInput.release(); // Débloque le thread pour permettre l'arrêt
wait();
}
void LoggerThread::postMessage(const QString &text)
{
m_bufferLock.lock();
// Formatage du message avec horodatage
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz");
m_logsQueue.append(QString("[%1] %2").arg(timeStamp, text));
m_bufferLock.unlock();
// Signale au thread qu'une nouvelle donnée est disponible
m_signalInput.release();
}
void LoggerThread::run()
{
while (m_isRunning)
{
// Attend qu'un message soit posté (bloquant, ne consomme pas de CPU)
m_signalInput.acquire();
if (!m_isRunning && m_logsQueue.isEmpty())
break;
QString logEntry;
// Extraction sécurisée du premier message
m_bufferLock.lock();
if (!m_logsQueue.isEmpty()) {
logEntry = m_logsQueue.takeFirst();
}
m_bufferLock.unlock();
if (!logEntry.isEmpty()) {
QFile file("application.log");
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
QTextStream stream(&file);
stream << logEntry << "\n";
file.close();
}
}
}
}
3. Utilisation dans l'application
Pour utiliser ce système, il suffit d'instancier le logger au démarrage de l'application et de démarrer son cycle de vie. On peut ensuite appeler la méthode d'écriture depuis n'importe quel endroit du code.
#include <QCoreApplication>
#include "loggerthread.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
// Initialisation du service de log
LoggerThread *logger = new LoggerThread();
logger->start();
// Exemple d'utilisation
logger->postMessage("Initialisation du système terminée.");
logger->postMessage("Connexion à la base de données établie.");
int result = app.exec();
// Nettoyage en fin de programme
delete logger;
return result;
}
Analyse des composants clés
Le fonctionnement repose sur trois piliers fondamentaux :
- Le Mutex (m_bufferLock) : Garantit que deux threads ne modifient pas la liste de messages simultanément, évitant ainsi les corruptions de mémoire.
- La File d'attente (m_logsQueue) : Sert de zone tampon (buffer) entre la production de logs rapides et l'écriture disque plus lente.
- Le Sémaphore (m_signalInput) : C'est le moteur de synchronisation. Contrairement à une boucle
while(true)classique qui consommerait 100% d'un cœur CPU, le sémaphore met le thread en sommeil tant qu'il n'y a rien à traiter.