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, 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 :

 
Sélectionnez
// 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.

 
Sélectionnez
#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 rendu 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 !