Les threads sans maux de tête

| Bradley T. Hughes | Qt Labs |

Traducteur

Jonathan Courtois

L'utilisation des threads vous donne des maux de tête ? Évitez-les en lisant cet article !

N'hésitez pas à commenter cet article !
Commentez

- Titre : Les threads sans maux de tête

- Auteurs : Bradley T. Hughes, Qt Labs

- Traducteur : Jonathan Courtois

- Parution : 21 août 2009

- Mise à jour : 10 février 2020

- Licence : Copyright ® 2010 Bradley T. Hughes. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Explication

- IV. Exemple

- V. Conclusion

- VI. Divers

I - L'article original

Les Qt Labs Blogs sont des blogs tenus par les développeurs de Qt, concernant les nouveautés ou les utilisations un peu extrêmes du framework.

Nokia, Qt et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction du billet Threading without the headache, par Bradley T. Hughes.

II - Introduction

Il y a quelques semaines, j'ai tenté de savoir si une fonction virtuelle pure pouvait être rendue non pure sans casser la compatibilité binaire. Vous me demanderez probablement pourquoi. Parce que je veux faire en sorte que QThread::run() appelle QThread::exec() par défaut. Nous savons tous que les threads sont difficiles à mettre en place, surtout en raison de la nécessité de verrouiller les données et de synchroniser les threads (avec un QWaitCondition ou un QSemaphore, par exemple). Toutefois, cette difficulté n'est pas nécessaire.

III - Explication

Considérons le code suivant :

// crée le producteur et le consommateur et les connecte entre eux
Producer producer;
Consumer consumer;
producer.connect(&consumer, SIGNAL(consumed()), SLOT(produce()));
consumer.connect(&producer, SIGNAL(produced(QByteArray *)), SLOT(consume(QByteArray *)));

// ils obtiennent tous les deux leur propre thread
QThread producerThread;
producer.moveToThread(&producerThread);
QThread consumerThread;
consumer.moveToThread(&consumerThread);

// c'est parti !
producerThread.start();
consumerThread.start();

La vie ne serait-elle pas merveilleuse si c'était aussi facile ? La bonne nouvelle ? C'est déjà le cas, si vous faites seulement une petite quantité de travail : réaliser une sous-classe de QThread et réimplémenter run() pour appeler simplement exec(). L'extrait de code ci-dessus provient d'un exemple où j'ai fait cela. Le résultat final est une solution au problème du Producteur/Consommateur sans la nécessité d'un verrou, d'une variable conditionnelle ou d'un sémaphore. Comment ? Je n'ai même pas eu besoin d'écrire un thread. Je peux tout faire d'une manière orientée objet. Le code du Producteur va à un endroit, celui du Consommateur dans un autre, puis je déplace tout cela dans un merveilleux thread boîte noire qui fait ce que j'attends.

IV - Exemple

Voici un code d'exemple de cette technique. Il est possible de le télécharger sous forme d'archive.

#include <QtCore>
#include <stdio.h>

enum {
Limit = 123456,
BlockSize = 7890
};

class Producer : public QObject
{
Q_OBJECT
QByteArray data;
int bytes;

public:
inline Producer() : bytes(0) { }

public slots:
void produce()
{
int remaining = Limit - bytes;
if (remaining == 0) {
emit finished();
return;
}

// this will never happen
if (data.size() != 0)
qFatal("Producer : consumer failed to consume!");

int size = qMin(int(BlockSize), remaining);
bytes += size;
remaining -= size;
data.fill('Q', size);

printf("Producer : produced %d more bytes, %d of %d total\n", size, bytes, Limit);
emit produced(&data);
}

signals:
void produced(QByteArray *data);
void finished();
};

class Consumer : public QObject
{
Q_OBJECT
int bytes;

public:
inline Consumer() : bytes(0) { }

public slots:
void consume(QByteArray *data)
{
// this will never happen
if (data->size() == 0)
qFatal("Consumer : producer failed to produce!");

int remaining = Limit - bytes;
int size = data->size();
remaining -= size;
bytes += size;
data->clear();

printf("Consumer : consumed %d more bytes, %d of %d total\n", size, bytes, Limit);
emit consumed();
if (remaining == 0)
emit finished();
}

signals:
void consumed();
void finished();
};

class QThreadEx : public QThread
{
protected:
void run() { exec(); }
};

int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);

// create the producer and consumer and plug them together
Producer producer;
Consumer consumer;
producer.connect(&consumer,
SIGNAL(consumed()),
SLOT(produce()));
consumer.connect(&producer,
SIGNAL(produced(QByteArray *)),
SLOT(consume(QByteArray *)));

// they both get their own thread
QThreadEx producerThread;
producer.moveToThread(&producerThread);
QThreadEx consumerThread;
consumer.moveToThread(&consumerThread);

// start producing once the producer's thread has started
producer.connect(&producerThread,
SIGNAL(started()),
SLOT(produce()));

// when the consumer is done, it stops its thread
consumerThread.connect(&consumer,
SIGNAL(finished()),
SLOT(quit()));
// when consumerThread is done, it stops the producerThread
producerThread.connect(&consumerThread,
SIGNAL(finished()),
SLOT(quit()));
// when producerThread is done, it quits the application
app.connect(&producerThread,
SIGNAL(finished()),
SLOT(quit()));

// go!
producerThread.start();
consumerThread.start();

return app.exec();
}

#include "main.moc"

V - Conclusion

Vous vous demandez sûrement à quoi tout cela sert. J'espère être en mesure de répondre à ma question initiale : est-ce qu'une fonction virtuelle pure peut être rendue non pure sans casser la compatibilité binaire ? Si oui, j'espère faire en sorte que QThread::run() appelle exec() dans Qt 4.3.

VI - Divers

Merci à dourouc05 ainsi qu'à jacques_jean pour leur relecture !