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 !