Un thread est une succession de commandes exécuté dans l'ordre pour aboutir à un résultat. Par exemple,
int main(int argc, char * argv[]) { int valeur; std::cin >> valeur; std::cout << 2* valeur; return 0; }
Le programme créé un thread qui va exécuter cette suite d'instruction. Ce thread est communément appelé le thread principal. Un programme multithread est donc simplement un programme qui va exécuter plusieurs threads en parallèle.
De manière plus précise, un programme multithread créé plusieurs threads qui vont se partager les ressources CPU d'un PC pour s'exécuter. La gestion de ces ressources est effectuée par l'OS. Sur un PC mono-coeur, les threads sont exécutés par le même processeur. Les threads ne sont donc pas vraiment exécutés en parallèle : l'OS va partager le processeur en exécutant des morceaux de chaque thread les uns après les autres. Sur un PC multicoeur, cette exécution est partagée par tous les processeurs.
Il est aussi important de savoir que :
La réponse n'est pas aussi évidente… Le multithreading n'est pas magique. Il est comme la force. L'utilisation de multithread n'est pas toujours égale à un gain de performance. Au contraire, elle complexifie le code et augmente le nombre de bugs potentiel. Par exemple, faire des copie de fichier en parallèle par plusieurs threads. Le point bloquant est les accès disques. Le multithreadding n'est pas spécialement pertinent.
Avant d'utiliser le mutithreading, il faut mieux comprendre pourquoi!! Mais seule l'expérience permet d'y répondre. Il est toujours conseillé d'utiliser le multithreading en dernier recours. Surtout lorsque l'on débute. La règle générale est : moins de threads moins de bugs. Qt, par sont fonctionnement permet quelques alternative qui évite l'utilisation de multithreading.
Le besoin de multithreading peux se découper en deux cas principale :
Faire fonctionner plusieurs objet en parallèle.
Pour la rapidité. Il faut savoir que chaque thread utilisé va ajouter du temps sur l’exécution de la tâche… Pour faire simple
Donc quand on exécute la tâche sur N threads, le temps effectif est T+N*t. Mais comme ce temps est répartie sur N threads, le temps d'un points de vue utilisateur est T / N + t. Si on utilise trop de thread que va t'il se passer? t va augmenter et à un moment on aura T < T / N + t On aura donc rien gagner et même perdu.
Voici quelques définitions à connaître avant d'aller plus loin dans ce tutoriel.
File d'exécution : succession d'instructions exécutées.
Event loop : Une eventloop est une boucle infinie qui exécute successivement une liste d'évènements qui évolue au cours du temps. Elle correspond à l'exécution d'un thread. Un évènement peut-être comparé à des callbacks qui sont appelés régulièrement au cours de l'exécution du programme. Le principe est assez simple :
Accès concurrent : un accès concurrent est un accès à une même ressource (mémoire, instance d'une classe, pointeur,…) par plusieurs threads. Ceci est le principale problème du multithreading. Une ressource accédée par plusieurs threads peut générer des problèmes insoupçonné. Par exemple, un pointeur est partagé entre plusieurs threads. Le premier thread effectue un traitement sur le pointeur. Au même instant, un second thread détruit ce pointeur pour le réinitialiser ⇒ le premier thread utilise un pointeur non valide ⇒ crash. Il faut donc protéger ces accés.
Re-entrant : De manière simplifié pour Qt, une fonction est dite reentrante si l'appel à cette fonction peut être appelée par différent threads s'il utilise leur propre données. Une fonction appartenant à une classe, peut être appeler par différente thread uniquement si l'instance de la classe est différente pour chaque thread.
Thread safe : une fonction est dite threadsafe si l'appel à cette fonction peut être appelé par différente thread sur une donnée partagée entre eux. Contrairement à la notion de re-entrance, une fonction appartenant à une classe peut être appelée par différents threads avec une même instance de la classe.
Opération Atomique : Une Opération Atomique est une opération complex dont l'exécution ne peux être bruité par un accès concurrent par les autres threads. On peut dire que l'Opérateur est thread-safe.
Ressources : pour ce tutoriel, une ressources peut être une instance de classes, une portion de code, un type primitif,….
Section critique : une section critique est un morceau de code partagé entre plusieurs threads et qui doit être exécuté par un seule thread à la fois.
Durée de vie : la durée de vie d'une instance et la portion de code dans laquelle cette instance existe. Par exemple un membre d'un classe à la même durée de vie que la classe et une instance d'une fonction ou d'un bloque {} à la durée de vie de cette fonction ou de ce bloque.
void f() { A a; ... if(..) { B b; ... //fin de la durée de vie de b } //fin de la durée de vie de a }
Synchrone : une exécution synchrone d'une classe signifie que l'appel as une de ses fonctions est immédiate et que le résultat voulue est obtenue une fois que la fonction exécuté. On peut aussi entendre parler de fonction bloquante.
Asynchrone : une exécution asynchrone d'une classe, signifie que son exécution est planifier et que le résultat sera obtenue à un autre instant. Avec Qt, cette planification s'effectue par une eventloop. On peut aussi entendre parler de fonction non-bloquante. Appartenance à un thread: toutes les classes héritant de QObject, appartiennent au thread qui la créée, et doivent uniquement être utiliser dans ce thread.
Emission d'un signal : les signaux de Qt sont thread safe et peuvent être utiliser par n'importe quelle thread.
Exécution d'un slot : l'exécution en multithreading d'un slot dépend du type de connexion. En règle générale, pour éviter tout problèmes d'accès concurrent, un slot est exécuté dans le thread auquel appartient l'instance de sa classe.
Connexion : une connexion entre threads peut être de différent type :
Avant de passer au multithreading, il existe quelques alternatives qui est bon de connaître. Mais qui ont leurs limites.
Pour éviter l'utilisation abusive de thread, beaucoup de classe Qt ont un fonctionnement asynchrone( QFtp, QProcess,…). Pour cela, ces classes utilisent l'eventloop pour planifier leur exécution en parallèle et ainsi partager le thread. C'est une méthode qui permet bien souvent d'éviter la création de thread secondaire inutile. Par exemple, la récupération d'un fichier par FTP :
Ainsi, l'utilisation de classe asynchrone évite la création d'un thread secondaire et évite de compliquer le code inutilement.
Une autre manière simple d'éviter l'utilisation de thread secondaire lors du traitement lourd ponctuelle, est d'exécuter, de temps à autre, l'exécution de l'eventloop du Thread principale avec la fonction static QCoreApplication::processEvents(). (lien faq http://qt.developpez.com/faq/?page=qt4Gui#de-geler-ihm) Il faut toute fois éviter d'appeler trop souvent cette méthode et il est assez difficile d'évaluer l'endroit le plus adapté pour l'appeler.
participant "calculer nombre \npremier" as A participant "event loop" as E loop A->A: action A->E: run eventloop end
QVector<int> maClass::monTraitementLourd(int max) { QVector<int> nombrePremier; nombrePremier <<1 << 2; for (int i = 3 ; i < max; i+=2) { //exécution de l'eventloop. QCoreApplication::processEvents(); bool estNombrePremier(true); for (int j =1 ; j < nombrePremier.size(); ++j) { if( i % nombrePremier[j] == 0) { estNombrePremier = false; break; } } if (estNombrePremier) { nombrePremier << i; } } return nombrePremier; }
La classe QProgressDialog permet de simuler ce fonctionnement en affichant une dialog contenant une progressBar. Pour cela il suffit de la rendre modal et d'appeler régulière la fonction setValue, qui va mettre a jour l'évolution de la progressBar et exécuter l'eventloop au besoin pour éviter l'effet “geler” de l'ihm.
participant "calculer nombre \npremier" as A participant "progress bar" as P participant "event loop" as E loop A->A : action A->P : next step alt si besoin P->E : run eventloop end end
QVector<int> maClass::monTraitementLourd(int max) { QVector<int> nombrePremier; nombrePremier <<1 << 2; //creation d'un progressdialog modale QProgressDialog progress("Calcule de nombre premier", Qstring(), 3, max); progress.setWindowModality(Qt::WindowModal); for (int i = 3 ; i < max; i+=2) { //Met à jour la progressbar et exécution de l'eventloop. progress.setValue(i); if (progress.wasCanceled()) break; bool estNombrePremier(true); for (int j =1 ; j < nombrePremier.size(); ++j) { if( i % nombrePremier[j] == 0) { estNombrePremier = false; break; } } if (estNombrePremier) { nombrePremier << i; } } return nombrePremier; }
La dernière est de découper le traitement en sous partie et d'utiliser l'eventloop pour son exécution. Pour cela, il est possible d'utiliser :
class calculNombrePremier : public QObject { //Q_OBJECT QVector<int> m_nombrePremier; int m_max; struct myEvent : public QEvent { static const QEvent::Type type; const int i; myEvent(int i):QEvent(type),i(i) {} }; public : void lancerCalcul(int max) { m_nombrePremier.clear(); m_max = max; m_nombrePremier <<1 << 2; //post un evenement dans l'eventloop pour exécuter le slot QCoreApplication::postEvent(this,new myEvent(3)); } QVector<int> nombrePremier() {return m_nombrePremier;} signals : // signal emit une fois le nombre max atteint. void finCalcul(); protected : bool event(QEvent *ev) { if(ev->type() == myEvent::type) { int nombreCourant = ((myEvent *)ev)->i; bool estNombrePremier(true); for (int j =1 ; j < m_nombrePremier.size(); ++j) { if( nombreCourant % m_nombrePremier[j] == 0) { estNombrePremier = false; break; } } if (estNombrePremier) { m_nombrePremier<< nombreCourant ; } //incrément du nombre courant pour le prochaine teste nombreCourant += 2; if(nombreCourant <=m_max) { //on post un evenement dans l'eventloop pour exécuter le slot QCoreApplication::postEvent(this,new myEvent(nombreCourant)); } else { qDebug() << m_nombrePremier; //sinon on as finie le traitement et on emet un signal. // emit finCalcul(); } return true; } return QObject::event(ev); } }; const QEvent::Type calculNombrePremier::myEvent::type = (QEvent::Type)QEvent::registerEventType ();
class calculNombrePremier : public QObject { Q_OBJECT QVector<int> m_nombrePremier; int m_max; int nombreCourant; public : void lancerCalcul(int max); { m_nombrePremier.clear(); m_max = max; nombrePremier <<1 << 2; nombreCourant = 3; //post un evenement dans l'eventloop pour exécuter le slot QTimer::SingleShot(0,this,SLOT(prochainTest())); } QVector<int> nombrePremier() {return m_nombrePremier;} signals : // signal emit une fois le nombre max atteint. void finCalcul(); private slots: { //test si le nombre courant est premier bool estNombrePremier(true); for (int j =1 ; j < m_nombrePremier.size(); ++j) { if( nombreCourant % m_nombrePremier[j] == 0) { estNombrePremier = false; break; } } if (estNombrePremier) { m_nombrePremier<< nombreCourant ; } //incrément du nombre courant pour le prochaine teste nombreCourant += 2; //tans que le nombre courant est inférieur au nombre max if(nombreCourant <=m_max) { //on post un evenement dans l'eventloop pour exécuter le slot QTimer::SingleShot(0,this,SLOT(prochainTest())); } else { //sinon on as finie le traitement et on emet un signal. emit finCalcul(); } } }
* void timerEvent(QTimerEvent*),startTimer et killtimer :
Le gros problème du multithreading est le partage de ressource entre plusieurs threads. Voici different exemple :
Il existe une infinité de problème lié à ces accès concurrent. Il faut donc les protéger.
le dead lock est un conséquence de l'utilisation de mutex qui va produire un bloquage de un ou plusieur threads indéfiniment. Par exemple, un thread bloque un mutex, mais pour une raison ou une autre, la libération n'est pas effectué. Si ce thread ou un autre essai de bloquer se mutex, il se trouve en attente de libèration pour le bloquer. Seulement comme le mutex n'as pas été précédement libéré, l'attente est infinie et le thread est bloqué. Un autre exemple, le partage de deux ressources entre deux threads. Le premier bloque les mutex dans un ordre et le second dans l'autre ordre. 0 un même instant les deux threads essaie de bloquer les deux mutex. Lorque le premier thread essaie de bloquer le deusième mutex et se retrouve bloqué jusqu'à la libération car l'utre thread le bloque déjà. Le deusième thread essai de bloquer l'autre mutex et se retrouve dans la mêm configuration. Les deux threads se sont bloqué mutuellement sans pouvoir se débloquer. deadlock.cpp
#include <QtCore> QMutex m1; QMutex m2; /*thread A : bloque m1 puis m2*/ class threadA : public QThread { protected : void run() { forever { qDebug() << "Thread A : essaie de bloquer m1"; m1.lock(); qDebug() << "Thread A : m1 bloque"; qDebug() << "Thread A : essaie de bloquer m2"; m2.lock(); qDebug() << "/******Thread A as bloque les deux mutex****/" << endl; m1.unlock(); m2.unlock(); } } }; /*thread B : bloque m2 puis m1*/ class threadB : public QThread { protected : void run() { forever { qDebug() << "\tThread B : essaie de bloquer m2"; m2.lock(); qDebug() << "\tThread B : m2 bloque"; qDebug() << "\tThread B : essaie de bloquer m1"; m1.lock(); qDebug() << "\t/*****Thread B as bloque les deux mutex****/" << endl; m2.unlock(); m1.unlock(); } } }; int main(int argc, char **argv) { //creation et lancement des threads threadA a; a.start(); threadB b; b.start(); //attente de la fin d'execution des deux threads a.wait(); b.wait(); }
Vous constaterez que le deadlock n'apparait toujours au mêm instant.
le live lock est un conséquence de l'utilisation du trylock qui peut généré qu'un thread n'est jamais ou difficilements les ressources qu'il as besoin. Par exemple, pour éviter le deadlock de l'exemple précédent, un trylock est utilisé pour essayer de bloquer le deusième mutex par les threads. Comme les deux thread essaie de bloquer les deux mutex en même temps, le trylock echoué régulièrement. Et les deux therad ont besoin de faire un grand nombre d'essai avant de réussir à bloque les deux mutex.
#include <QtCore> QMutex m1; QMutex m2; /*thread A : bloque m1 puis m2*/ class threadA : public QThread { protected : void run() { int nbTest (0); forever { m1.lock(); if (m2.tryLock()) { qDebug() << "/***Thread A bloque les deux mutex apres "<<nbTest<<" *******/" << endl; m2.unlock(); nbTest = 0; } else { ++nbTest; } m1.unlock(); } } }; /*thread B : bloque m2 puis m1*/ class threadB : public QThread { protected : void run() { int nbTest (0); forever { m2.lock(); if (m1.tryLock()) { qDebug() << "\t/***Thread B bloque les deux mutex aprs "<<nbTest<<" *******/" << endl; m1.unlock(); nbTest = 0; } else { ++nbTest; } m2.unlock(); } } }; int main(int argc, char **argv) { //creation et lancement des threads threadA a; a.start(); threadB b; b.start(); //attente de la fin d'execution des deux threads a.wait(); b.wait(); }
Pour pourrez voir le nombre de teste avant réussite qui peut être trés élevé. Dans certaine condition ce nombre pourrais devenir infinie.
Un mutex est une classe therad safe qui permet de savoir si une ressource est utilisée ou non par un autre thread et permet de résoudre le problème d'accés concurrent. La classe Qt correspodante est le QMutex. Cette classe possède divers fonctionnalités :
De plus, lors de sa création, il est possible de spécifier s'il as un comportement récursive ou non récursive. Le comportement récursive, signifie qu'un thread peut locker plusieur fois un mutex. Le mutex sera libéré àpres le même nombre de libération. Contrairement au comportement non récursive qui ne permet à un thread que de le bloquer qu'une fois. Pour des raisons de performance et comme il est plustôt rare qu'un thread doit bloquer plusieurs fois un même mutex, le comportement par défaut est le non récursive.
#include <QtCore> QMutex mRecursive( QMutex::Recursive); QMutex mNonRecursive; /*thread A : bloque récursivement un mutex et le libère*/ class threadA : public QThread { QMutex &m; public : threadA(QMutex &mutex):m(mutex){} protected : void run() { int nbLocked =0; /*bloque le mutex recursivement tout les 500ms*/ for (int i = 0 ; i<5; ++i) { qDebug()<< "Thread A : essai de bloquer le mutex"; m.lock(); ++nbLocked; qDebug()<< "Thread A : mutex bloque "<<nbLocked <<"fois"; msleep(500); } /*libere le mutex */ for (int i = 0 ; i<5; ++i) { m.unlock(); --nbLocked; qDebug()<< "Thread A : mutex libere - mutex encore bloque "<<nbLocked <<"fois"; } } }; /*thread A : bloque le mutex*/ class threadB : public QThread { QMutex &m; public : threadB(QMutex &mutex):m(mutex){} protected : void run() { /*pour etre sure que l'autre thread as bloquer le mutex*/ sleep(1); qDebug()<< "\tThread B : essai de bloquer le mutex"; /*bloque le mutex - attente de la liberation*/ m.lock(); qDebug()<< "\tThread B : mutex bloquer"; m.unlock(); } }; int main(int argc, char **argv) { qDebug() <<"/****** TEST D'UN MUTEX RECURSIVE *********/"; { //creation et lancement des threads threadA a(mRecursive); a.start(); threadB b(mRecursive); b.start(); //attente de la fin d'execution des deux threads a.wait(); b.wait(); } qDebug() <<endl; qDebug() <<"/****** TEST D'UN MUTEX NON RECURSIVE ****/"; { //creation et lancement des threads threadA a(mNonRecursive); a.start(); threadB b(mNonRecursive); b.start(); //attente de la fin d'execution des deux threads a.wait(); b.wait(); } return 0; }
La première partie montre le fonctionnement du mutex recursive : le therad A va bloquer le mutex recursivement et le libère. Le thread B essaie de bloquer le mutex et va ainsi attend la libération du mutex pour le bloquer Le second montre le fonctionnement du mutex non recursive : le therad A va bloquer le mutex une première fois sans problème. Le deusième bloque sur le mutex va bloquer le thread A car le mutex est déjà bloqué. Le thread B essaie de bloquer le mutex. Comme le thread est bloqué, il ne pourra jamais libèrer le mutex. Les deux thread sont bloqué. Ceci est plus communément appeler dead lock. En mode debug, Qt vous écrira une jolie erreur dans la console “QMutex::lock: Deadlock detected in thread …”
Pour simplifier la manipulation des mutex est éviter une libération oublié, Qt fournie la class QMutexLocker représentant une implementation RAII pour le mutex, qui va bloquer le mutex lors de ca création et le libérer lors de sa destruction. Il est aussi possible de libérer et rebloquer le mutex pendant sa durée de vie.
QMutex lock; QMutex lock2; void f() { //locker bloque le mutex lock QMutexLocker locker(lock); // on bloque le mutex lock2 lock2.lock(); //fonction qui génère un exeption uneFonction(); // fonction non appelée suite à l'exception lock2.unlock(); // locker est détruit et va libérer lock // malheureusement lock2 est toujours bloqué }
Accés en lecture : accés qui ne va produire aucune modification de la ressource. Accés en écriture: accés qui peut produire des modifications de la ressource.
L'accés concurrent à une données est un problème uniquement si l'un des accés est en écriture. L'accés current en lecture par plusieur thread ne pause aucun problème. Le QReadWriteLock, est un mutex qui va prendre en compte cette propriété. Ainsi, il peut être bloqué par plusieurs threads simultabément pour des accés en lecture et bloquer par un seule thread pour un accés en écriture. Comme QMutex, cette classe fournit une base :
De plus Qt fournie aussi deux classes similaire à QMutexLocker pour simplifier sa manipulier :
1: #include <QtCore> 2: QReadWriteLock lock; 3: QVector<int> vect(10); 4: 5: 6: /*thread A : affiche le vect ou l'initialise avec une suite n = n+1*/ 7: class threadA : public QThread 8: { 9: 10: protected : 11: void run() 12: { 13: 14: forever 15: { 16: //affiche ou initialise vect de maniere aleatoire 17: if (qrand()%2) 18: { 19: qDebug() << "Thread A essai lecture "; 20: //lock en lecture 21: QReadLocker rl(&lock); 22: QString s; 23: 24: foreach(int i ,vect) 25: { 26: s += QString::number(i) + " "; 27: } 28: 29: qDebug() << "Thread A : " <<s; 30: msleep(100); 31: } 32: else 33: { 34: qDebug() << "Thread A essai ecriture "; 35: //lock en ecriture 36: QWriteLocker wl(&lock); 37: 38: int id = 0; 39: for( int i = 0 ; i < vect.size() ; ++i) 40: { 41: vect[i] = id++; 42: } 43: qDebug() << "Thread A as ecrit"; 44: msleep(100); 45: } 46: } 47: } 48: }; 49: 50: /*thread A : affiche le vect ou l'initialise avec une suite aléatoire*/ 51: class threadB : public QThread 52: { 53: 54: protected : 55: void run() 56: { 57: forever 58: { 59: 60: if (qrand() % 2) 61: { 62: qDebug() << "\tThread B essai lecture "; 63: //lock en lecture 64: QReadLocker rl(&lock); 65: QString s; 66: foreach(int i ,vect) 67: { 68: s += QString::number(i) + " "; 69: } 70: qDebug() << "\tThread B : " <<s; 71: msleep(100); 72: } 73: else 74: { 75: qDebug() << "\tThread B essai ecriture "; 76: //lock en ecriture 77: QWriteLocker wl(&lock); 78: 79: for( int i = 0 ; i < vect.size() ; ++i) 80: { 81: vect[i] = qrand()%100; 82: } 83: qDebug() << "\tThread B as ecrit"; 84: msleep(100); 85: } 86: } 87: } 88: }; 89: int main(int argc, char **argv) 90: { 91: //creation et lancement des threads 92: threadA a; a.start(); 93: threadB b; b.start(); 94: 95: //attente de la fin d'execution des deux threads 96: a.wait(); b.wait(); 97: }
Le sémaphore est une généralisation du mutex. Contrairement au mutex qui sert à protéger une seule ressource, le semaphore est une protection d'un ensemble de ressources de même type. on retrouve des notions équivalente :
Contrairement au mutex, la manipulation d'un sémaphore est répartie entre toute les threads. C'est à dire qu'un thread peut demander l'aquisition et un autre thread la libèration. Cette méthode, plus difficile à comprendre, est potentiellement plus rapide, car l'accés en lecture/écriture à l'ensemble des ressources peut être différente pour chaque ressources.
Exemple d'un buffer circulaire entre deux thread où la ressource correspond à un élément du buffer. Pour cela on va utiliser deux sémaphores pour connaitre le nombre de “ressources libres” et de “ressources utilisées”:
Les deux thread vont se partager ces sémaphores pour protéger les accés au buffer circulaire :
Le point important sont les étapes 1 et 3 des thread qui s'oppose!!! ce qui permet la gestion du bocage des threads Bloquage de A
Bloquage de B
1: #include <QtCore> 2: #include <iostream> 3: 4: //taille du buffer circulaire 5: const int bufferSize = 1000; 6: //buffer circulaire de taille fixes 7: int buffer[bufferSize]; 8: 9: //booleen pour stopper l'execution aprés l'appuie de la touche enter 10: bool stopThread = false; 11: 12: //nombre de ressources utilisable 13: //limite à la taille du buffer circulaire 14: QSemaphore ressourceUtilisable (bufferSize); 15: 16: //nombre de ressources librable. 17: //Au debut, aucune donnees de peut être libérées 18: QSemaphore ressourceLiberable ; 19: 20: //Demande à ressourceUtilisable la possibiliter d'utiliser n ressources 21: void AcquisitionNRessources(qint32 n) 22: { 23: ressourceUtilisable.acquire(n); 24: } 25: //Mise a jour du nombre de ressources liberable 26: void NRessourcesUtilisees(qint32 n) 27: { 28: ressourceLiberable.release(n); 29: } 30: 31: //thread A : écrit dans le buffer 32: //Aquit N ressources 33: //Ecrire dans le buffer 34: //Met à jour le nombre de ressources liberable 35: class threadA : public QThread 36: { 37: 38: protected : 39: void run() 40: { 41: qsrand( QTime().secsTo(QTime::currentTime()) ); 42: int idBuffer(0); 43: while(!stopThread) 44: { 45: int nbRessource = 1 + qrand() % (bufferSize / 10); 46: qDebug()<<"Thread A va aquerir "<<nbRessource<<" ressources"; 47: AcquisitionNRessources(nbRessource); 48: qDebug()<<"Thread A ecrit dans le buffer [" 49: <<idBuffer<<" , "<< (idBuffer + nbRessource) % bufferSize << "]"; 50: for (int i = 0; i < nbRessource; ++i) 51: { 52: buffer[idBuffer] = qrand() % 100; 53: idBuffer = (idBuffer +1) % bufferSize; 54: } 55: msleep(500 ); 56: qDebug()<<"Thread A met a jour le nombre de ressources liberables"; 57: NRessourcesUtilisees(nbRessource); 58: } 59: } 60: }; 61: 62: //Demande à ressourceLiberable la possibiliter de liberer n ressources 63: void LibererationNRessources(qint32 n) 64: { 65: ressourceLiberable.acquire(n); 66: } 67: 68: //Mise a jour du nombre de ressources utilisable 69: void NRessourcesLiberees(qint32 n) 70: { 71: ressourceUtilisable.release(n); 72: } 73: 74: // Thread B :lit dans le buffer 75: //Libere N ressources 76: //lit dans le buffer 77: //Met à jour le nombre de ressources utilisable 78: class threadB : public QThread 79: { 80: protected : 81: void run() 82: { 83: qsrand( QTime().secsTo(QTime::currentTime()) ); 84: int idBuffer(0); 85: while(!stopThread) 86: { 87: 88: int nbRessource =1+ qrand() % (bufferSize / 10); 89: 90: qDebug()<<"\tThread B va liberer "<<nbRessource<<" ressources"; 91: LibererationNRessources(nbRessource); 92: 93: qDebug()<<"\tThread B lit dans le buffer [" 94: <<idBuffer<<" , "<< (idBuffer + nbRessource) % bufferSize << "]"; 95: for (int i = 0; i < nbRessource; ++i) 96: { 97: 98: idBuffer = (idBuffer +1)%bufferSize; 99: } 100: msleep(500 ); 101: 102: NRessourcesLiberees(nbRessource); 103: qDebug()<<"\tThread B as libere "<<nbRessource<<" ressources"; 104: } 105: } 106: }; 107: int main(int , char **) 108: { 109: //creation et lancement des threads 110: threadA a; a.start(); 111: threadB b; b.start(); 112: 113: std::string s; 114: std::getline(std::cin , s); 115: stopThread = true; 116: //attente de la fin d'execution des deux threads 117: a.wait(); b.wait(); 118: }
On constate que les deux threads utilisent à chaque instant des zones totalement différente du buffer circulaire et qu'ils s'éxécute bien en parralèlle.
Remarque : Le sémaphore est sousmis au même type problème du multi threading.Dans l'exemple, l'aquisition de ressources est limitées pour éviter un l'interblocage des deux threads :
Pour tester, il vous suffit de remplacer les lignes
int nbRessource =1+ qrand() % (bufferSize / 10);
par
int nbRessource =1+ qrand() % bufferSize ;
Au lancement vous verrez trés vite que les deux threads se retrouvent bloqués.
En sois même, QThread n'est pas un thread. QThread est une classe qui sert d'interface à un thread. C'est lui qui lance le thread et le manipule (connaitre son état, change sa priorité,…). Une instance de QThread n'est donc pas exécuté dans le thread. Excepté la fonction run. En effet lorsque QThread démarre un thread, il lui passe sa fonction run en paramètre pour lui faire exécuter. Le context effectif du thread se situe entre le début et la fin de la fonction run. Les principales fonctions sont :
Cette classe permet aussi d'accéder à quelques information très pratique par quelques fonctions static :
C'est le fonctionnement de base d'un thread. Le thread ne fait qu'exécute la fonction run du QThread. Pour cela il suffit de créé une classe héritant du QThread et de redéfinir la méthode run.
Comme run fait partie de QThread, on peut accéder à ses fonction. Malheureusement comme le QThread ne s’exécute pas dans le thread, utiliser ces fonctions peuvent créer des accès concurrent. Ceci est donc à éviter voir à proscrire. Toute fois, il faut se rappeler que les signaux sont thread-safe et peuvent donc être exécuter par n'importe quel thread!! On peut donc utiliser les signaux définie pas la classe héritant de QThread pour emettre des signaux à partir su thread. Ce qui ne manque pas d'intêret
Pour résumer il faut éviter d'utiliser les fonctions du QThread dans le thread à l'éxécption des signaux qui eux sont thread-safe!!!.
Le calcul de nombre premier peut ainsi devenir
1: class monThread : public QThread 2: { 3: Q_OBJECT 4: int m_max; 5: public : 6: monThread(int max) : m_max(max){}; 7: 8: signals : 9: //valeur entre 0 et 1 indiquant l'avancement du calcul 10: void avancement(double); 11: //Les nombre premier trouvés 12: void resultat(const QVector<int> &); 13: 14: protected : 15: void run() 16: { 17: QVector<int> nombrePremier; 18: nombrePremier <<1 << 2; 19: 20: //copie de la valeur max. 21: int max (m_max) 22: for (int i = 3 ; i < max; i+=2) 23: { 24: 25: bool estNombrePremier(true); 26: for (int j =1 ; j < nombrePremier.size(); ++j) 27: { 28: if( i % nombrePremier[j] == 0) 29: { 30: estNombrePremier = false; 31: break; 32: } 33: } 34: if (estNombrePremier) 35: { 36: nombrePremier << i; 37: } 38: 39: emit avancement (1. * i / m_max); 40: } 41: emit avancement (1.); 42: emit resultat(nombrePremier); 43: } 44: }
Lors de son instanciation, un QObject est associé avec le QThread qui le construit. Ceci implique que cette objet devrait être utilisé uniquement pas ce QThread!!! Cette propriété est très importante surtout pour le système de signal/slot. En effet, lorsque la connexion est en mode automatique, si l'emmeteur et le receveur se sont pas associé avec le même QThread, un évènement est ajouté à l'eventloop du QThread associé au receveur. Ainsi l'exécution su slot sera sans le bon thread.
Il suffit alors de créer un QThread où tous les QObjects sont créent au début du run et terminé par l'appel à exec() pour lancer l'eventloop.
class monThread : public QThread { ... protected : void run() { //création des QObject; ... //création des connection; ... //lancement de l'eventloop exec() } };
Par cette méthode, la création des connexion n'est pas évidente lorsque l'on doit connecté des QObject du thread avec des QObject externe et amène souvent à des erreur de programmation. L'association d'un QObject avec un QThread peut être modifié grâce à la méthode QObject::moveToThread(QThread *) qui peut simplifier grandement les choses et même rendre l'utilisation d'eventloop-QThread encore plus puissante.
L'utilisation d'une eventloop+QThread et de moveToThread permet de simplifier la connexion entre plusieurs QObjects et d'utiliser les QThread comme des espaces d'exécution des QObject. Pour cela il faut :
Les QObject utilisés doivent avoir certaine propriété :
Cette façon de faire as aussi d’autre avantage :
Astuce : Il peut être embêtant de devoir créer des signaux uniquement pour appeler un slot de la classe et un appel direct à la fonction est plus intéressant. Dans ce cas, il faut protéger l'exécution de cette fonction. Avec la classe précédente, comme la fonction est un slot et que le résultat est retourné par un signal, il est très simple de protéger l’appel directement. Il vous faut créer un QObject que ne définie que des signaux et dont la classe est déclaré comme amis (friends). Les signaux ont la même signature que les slots de la classe. Il suffit alors :
Voici un exemple illustrant les explications. Le checkbox moveToThread permet de passer d’un fonctionnement mono-thread à multi-thread. Un click sur l’image lnce une génération.
#include <QtGui> #include <complex> const int R_2 = 100000; const int MAX_TEST = 255; const int IMG_W = 800; const int IMG_H = 800; class Generator; //class interne à generator. Utilisé pour déplacer l'appel d'un slot dans le bon thread. class GMoveCallToThread : public QObject{ Q_OBJECT public : GMoveCallToThread(QObject * parent) :QObject(parent) {} //réplique les slots de Generator sous forme de signal signals : void generateImage(const std::complex<double> & cc); void useThread(QThread *t); //generator doit pouvoir utiliser directement les signaux friend class Generator; }; //class qui génère une image. class Generator : public QObject { Q_OBJECT //Instance utilisé pour déplacer l'appel d'un slot dans le bon thread. GMoveCallToThread * m_moveCallToThread; //indique si un traitement est en cours ou non bool m_running; public : Generator(QObject * parent = 0):QObject(parent),m_moveCallToThread(new GMoveCallToThread(this)),m_running(false) { //on enregistre la classe std::complex<double> //pour l'utilisation des signal/slot en multi-thrread qRegisterMetaType<std::complex<double> >("std::complex<double>"); //on connecte les slots avec leurs signaux correspondant connect(m_moveCallToThread,SIGNAL(generateImage(const std::complex<double> &)),this,SLOT(generateImage(const std::complex<double> &))); connect(m_moveCallToThread,SIGNAL(useThread(QThread *)),this,SLOT(useThread(QThread *))); }; //spécifie si un traitement est en cours. bool running() {return m_running;} public slots : //genère une image de julian avec cc comme nombre constant. void generateImage(const std::complex<double> & cc) { //Si l'appel est dans le mauvais thread, //on déplace l'appel vers le bon thread. if(QThread::currentThread() != thread()) { m_moveCallToThread->generateImage(cc); return; } m_running = true; //Calcule de l'image de julian QImage img(IMG_W,IMG_H,QImage::Format_ARGB32); img.fill(0); int max = 0; QTime t;t.start(); for(int i = 0; i < IMG_H / 2; ++i) { for(int j = 0; j < IMG_W; ++j) { std::complex<double> c = cc ; std::complex<double> z ( -2. + 4. * j / (IMG_W - 1), 2. - 4. * i / (IMG_H - 1) ); int nbTest = 0; while(std::abs(z) < R_2 && nbTest++ < MAX_TEST ) { z = z*z + c; } if(nbTest< MAX_TEST ) { if (nbTest > max) max = nbTest; img.setPixel(j, i, nbTest); img.setPixel((IMG_W - 1 - j) , (IMG_H - 1 - i) , nbTest); } } //si 40 ms est passé on envoye une image temporaire if(t.elapsed()>40) { emit tmpresult(img,max); t.restart(); } } //envoie l'image finale emit result(img,max); m_running = false; } //change l'appartenance du générator à un thread. void useThread(QThread *t) { //Si l'appel est dans le mauvais thread, //on déplace l'appel vers le bon thread. if(QThread::currentThread() != thread()) { m_moveCallToThread->useThread(t); return; } moveToThread(t); } signals: //envoie de l'image finale. void result(const QImage &,int); //envoie de l'image intermédiaire. void tmpresult(const QImage &,int); }; //widget d'affichage. class maWidget : public QWidget { Q_OBJECT //affiche la valeur complex en fonction de la position de la souris. QLabel * m_c; //affiche l'image QLabel * m_imgDisplay; //complexe d'initialisation utilisé pour la génération std::complex<double> m_z0; //Generateur. Generator m_gen; //thread secondaire. QThread m_thread; public : maWidget() { QVBoxLayout * layout = new QVBoxLayout(this); { //checkbox permettant de spacifie rle thread d'éxécution du gnérateur QCheckBox * cb = new QCheckBox("MoveToThread"); connect (cb,SIGNAL(toggled(bool)),this,SLOT(moveThread(bool))); layout->addWidget(cb); m_c = new QLabel("<b>Cliquer sur l'image</b>"); layout->addWidget(m_c); m_imgDisplay = new QLabel(); m_imgDisplay->setMouseTracking(true); m_imgDisplay->setPixmap(QPixmap(IMG_W,IMG_H)); m_imgDisplay->installEventFilter (this); layout->addWidget(m_imgDisplay); connect(&m_gen,SIGNAL(result(const QImage &,int)), this, SLOT(displayResult(const QImage &,int))); connect(&m_gen,SIGNAL(tmpresult(const QImage &,int)), this, SLOT(displayIntermediaire(const QImage &,int))); } //on lance le thread secondaire. m_thread.start(); } //filter les evenement souris du label qui affiche l'image bool eventFilter(QObject *obj, QEvent *event) { if(event->type() == QEvent::MouseMove) { //si la souris bouge, on affiche la valeur complex corespondante à la position de la souris. QMouseEvent * me = (QMouseEvent *)event; std::complex<double> c(-2. + 4.* me->x()/(IMG_W - 1),2. - 4.* me->y()/(IMG_H - 1)); m_c->setText(QString("<b>Cliquer sur l'image</b> : { %1 , %2 }").arg(c.real()).arg(c.imag())); } else if(event->type() == QEvent::MouseButtonRelease && !m_gen.running()) { //Si le boutton est relaché et que le générateur est inactif, on lance une nouvelle génération. QMouseEvent * me = (QMouseEvent *)event; m_z0 = std::complex<double> (-2. + 4.* me->x() / (IMG_W - 1), 2. - 4.* me->y() / (IMG_H - 1)); m_gen.generateImage(m_z0); } return QObject::eventFilter(obj, event); } ~maWidget() { //on stop le thread et on attend sa fin. m_thread.quit (); m_thread.wait(); } //convertie une image reçu en QPixmap à afficher. QPixmap imgtoPixmap(QImage img,int max) { //Creation d'une LUT pour convertir les valeurs uint des pixels de img en couleur. QColor c(Qt::blue); QVector<quint32> LUT(max+1); LUT[0] = qRgba(0,0,0,255); for(int i =1; i < LUT.size();++i) { double p = pow(double(i) / max, .9); if(p>1.) p =1.; c.setHslF(c.hueF(), c.saturationF(), pow(p,.5)); LUT[i] = c.rgba(); } //Application de la LUT sur les pixel for(int i = 0; i < img.height(); ++i) { for(int j = 0; j < img.width(); ++j) { img.setPixel(j,i, LUT[img.pixel(j,i)]); } } //Affiche la valeur complex d'initialisation utilisée. { QPainter p(&img); p.setPen(Qt::red); p.drawText(5,15,QString("{ %1 , %2 }").arg(m_z0.real()).arg(m_z0.imag())); } return QPixmap::fromImage(img); } public slots : //affiche l'image résultat reçu. void displayResult(const QImage & img,int max) { m_imgDisplay->setPixmap(imgtoPixmap(img, max)); } //affiche l'image intermédiaire reçu. void displayIntermediaire(const QImage & img,int max) { m_imgDisplay->setPixmap(imgtoPixmap(img, max)); } //change le thread du générateur. void moveThread(bool b) { if(b) { m_gen.useThread(&m_thread); } else { m_gen.useThread(thread()); } } }; #include "main.moc" int main(int argc, char *argv[]) { //initialisation de la graine aléatoire. qsrand(QTime::currentTime().msecsTo(QTime())); QApplication a(argc, argv); maWidget w; w.show(); return a.exec(); }
Remarque : on pourrais être tenté de créer un QObject à partir de QThread et d’utiliser la fonction moveToThread sur lui même en début du run pour exécuter ses slots dans la thread. Seulement cela poserai des problèmes : par exemple, pour redémarrer la thread après une première exécution, si vous utilisez le start comme un slot.
Lorsque que l'on doit éxécuter de petit morceau de code très régulièrement, on essaie d'éviter la création de thread et de réutiliser les threads. Pour cela Qt propose les classes :
Lorsque l'on veux exécuter du code dans un thread d'un QThread pool, il faut créé un QRunnable et de ré-implémenter la fonction run.
class monCode : public QRunnable { protected : void run() { //le code à éxécuter } };
Par défaut un QRunnable doit être instancié par un new et est détruit à la fin de leur exécution. Toute fois, QRunnable possède la fonction setAutoDelete qui permet de spécifier que c'est vous qui vous chargez de sa destruction (Attention à sa durée de vie)
Pour éxécuter un QRunnable QThreadPool propose deux fonctions :
Code d'exemple
http://doc.qt.nokia.com/4.7-snapshot/qtconcurrent.html Lors du lancement d'un application, Qt alloue un pool de thread pas défaut adapté au nombre de coeur de la machine. Les QtConcurrents sont des algorithms style STL qui parallélise leurs exécutions grâce au pool de thread globale. C'est une API de haut niveau qui permet de faire abstraction des threads. Ils sont adaptés à un besoin ponctuelle de thread. Par exemple la création de preview d'un ensemble d'image. Pour bien utiliser les QtConcurrent il y as quelques classes importante à connaitre :
Dans la version 4.7 on trouve principalement trois type d'algorithmes.
Les algorithmes qui filtre les éléments d'une séquence (QVector,QList,std::vector,…) par un prédicat.
#include <QtCore> struct predicat { bool operator()(const QString &s) { return s[0].isUpper(); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<void> f = QtConcurrent::filter(myList,predicat()); f.waitForFinished(); qDebug() <<"apres : " << myList; return 0; }
#include <QtCore> struct predicat { bool operator()(const QString &s) { return s[0].isUpper(); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<QString> f = QtConcurrent::filtered(myList,predicat()); f.waitForFinished(); qDebug() <<"res : " << f.results(); return 0; }
#include <QtCore> struct predicat { bool operator()(const QString &s) { return s[0].isUpper(); } }; struct reduce { void operator()(QString &res, const QString &s) { res.append(" ").append(s); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<QString> f = QtConcurrent::filteredReduced<QString>(myList,predicat(),reduce()); f.waitForFinished(); qDebug() <<"res : " << f.result(); return 0; }
Les algorithmes qui applique un foncteur sur les éléments d'une séquence (QVector,QList,std::vector,…).
#include <QtCore> struct foncteur { void operator()( QString &s) { s = s.toUpper(); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<void> f = QtConcurrent::map(myList,foncteur()); f.waitForFinished(); qDebug() <<"apres : " <<myList; return 0; }
#include <QtCore> struct foncteur { typedef QString result_type; QString operator()(const QString &s) { return s.toUpper(); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<QString> f = QtConcurrent::mapped(myList,foncteur()); f.waitForFinished(); qDebug() <<"results : " <<f.results(); return 0; }
#include <QtCore> struct foncteur { typedef QString result_type; QString operator()(const QString &s) { return s.toUpper(); } }; struct reduce { void operator()(QString &res, const QString &s) { res.append(" ").append(s); } }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); QStringList myList = QStringList() <<"foo"<<"Bar"<<"DVP"<<"qt"; qDebug() <<"avant : " << myList; QFuture<QString> f = QtConcurrent::mappedReduced<QString>(myList,foncteur(),reduce()); f.waitForFinished(); qDebug() <<"result : " <<f.result(); return 0; }
Le dernier est l'algorithmes QtConcurrent::run qui permet d’exécuter une méthode ou une méthode d'une instance dans un thread. Il est aussi possible de passer des paramètres à la méthodes.
Toute ces méthodes retournent un QFuture. Toute fois, les algorithmes qui parcoure une séquence ou sous séquence (rang d'itérateur) propose des version alternative nommé blokingXXX avec XXX le nom de l'algorithme. Ces versions attentent la fin de l’exécution de l’algorithme et retourne le résultat.
Voici un exemple complet
#include <QtGui> #include <complex> #include <algorithm> const int R_2 = 100000; const int MAX_TEST = 255; const int IMG_W = 800; const int IMG_H = 800; struct zone { QSize pixelZone; QRect subPixelZone; zone( const QSize &pixelZone, const QRect & subPixelZone) :pixelZone(pixelZone),subPixelZone(subPixelZone) {} zone( const zone & z) :pixelZone(z.pixelZone),subPixelZone(z.subPixelZone) {} zone(){} }; struct zoneInfo { zone z; int max; zoneInfo( const zone & z,int max) :z(z),max(max) {} zoneInfo():max(0){} }; struct Operator { typedef std::pair<QImage,zoneInfo> result_type; Operator(const std::complex<double> &c ):c(c){} std::complex<double> c ; std::pair<QImage,zoneInfo> operator()(const zone &z) { QImage img(z.subPixelZone.size(),QImage::Format_ARGB32); int max = 0; const int ib = z.subPixelZone.top(); const int ie = z.subPixelZone.bottom()+1; const int jb = z.subPixelZone.left(); const int je = z.subPixelZone.right()+1; for(int i = ib; i < ie; ++i) { for(int j = jb; j < je; ++j) { std::complex<double> z ( -2. + 4. * j / (IMG_W - 1), 2. - 4. * i / (IMG_H - 1) ); int nbTest = 0; while(std::abs(z) < R_2 && nbTest++ < MAX_TEST ) { z*=z; z+=c; } if(nbTest< MAX_TEST ) { if (nbTest > max) max = nbTest; img.setPixel(j-jb, i-ib, nbTest); } else { img.setPixel(j-jb, i-ib, 0); } } } return std::make_pair(img,zoneInfo(z,max)); } }; //widget d'affichage. class maWidget : public QWidget { Q_OBJECT //affiche la valeur complex en fonction de la position de la souris. QLabel * m_c; //affiche l'image QLabel * m_imgDisplay; //complexe d'initialisation utilisé pour la génération std::complex<double> m_z0; QFutureWatcher<Operator::result_type> m_watcher; QList<zone> lz; QImage m_res; int max; QTimer m_t; public : maWidget() :m_res(IMG_W,IMG_H,QImage::Format_ARGB32),max(0) { m_res.fill(0); QVBoxLayout * layout = new QVBoxLayout(this); { m_c = new QLabel("<b>Cliquer sur l'image</b>"); layout->addWidget(m_c); m_imgDisplay = new QLabel(); m_imgDisplay->setMouseTracking(true); m_imgDisplay->setPixmap(QPixmap(IMG_W,IMG_H)); m_imgDisplay->installEventFilter (this); layout->addWidget(m_imgDisplay); connect(&m_watcher,SIGNAL(resultReadyAt(int)), this, SLOT(addResult(int))); connect(&m_watcher,SIGNAL(finished()), this, SLOT(display())); } for (int i =0; i<IMG_H; i+=10) { for(int j =0; j<IMG_W; j+=10) { lz << zone( QSize (IMG_H,IMG_W), QRect(j, i, 10, 10) ); } } std::random_shuffle(lz.begin(),lz.end()); m_t.setInterval(100); m_t.setSingleShot(false); connect(&m_t,SIGNAL(timeout()), this, SLOT(display())); connect(&m_watcher,SIGNAL(finished()), &m_t, SLOT(stop())); connect(&m_watcher,SIGNAL(canceled()), &m_t, SLOT(stop())); } //filter les evenement souris du label qui affiche l'image bool eventFilter(QObject *obj, QEvent *event) { if(event->type() == QEvent::MouseMove) { //si la souris bouge, on affiche la valeur complex corespondante à la position de la souris. QMouseEvent * me = (QMouseEvent *)event; std::complex<double> c(-2. + 4.* me->x()/(IMG_W - 1),2. - 4.* me->y()/(IMG_H - 1)); m_c->setText(QString("<b>Cliquer sur l'image</b> : { %1 , %2 }").arg(c.real()).arg(c.imag())); } else if(event->type() == QEvent::MouseButtonRelease) { m_watcher.cancel(); m_res.fill(0); max = 0; m_t.start(); m_imgDisplay->setPixmap(QPixmap(IMG_W,IMG_H)); //Si le boutton est relaché et que le générateur est inactif, on lance une nouvelle génération. QMouseEvent * me = (QMouseEvent *)event; m_z0 = std::complex<double> (-2. + 4.* me->x() / (IMG_W - 1), 2. - 4.* me->y() / (IMG_H - 1)); m_watcher.setFuture(QtConcurrent::mapped(lz,Operator(m_z0))); } return QObject::eventFilter(obj, event); } ~maWidget() { } //convertie une image reçu en QPixmap à afficher. QPixmap imgtoPixmap(QImage img,int max) { if(max ==0) return QPixmap(); //Creation d'une LUT pour convertir les valeurs uint des pixels de img en couleur. QColor c(Qt::blue); QVector<quint32> LUT(max+1); LUT[0] = qRgba(0,0,0,255); for(int i =1; i < LUT.size();++i) { double p = pow(double(i) / max,.5); if(p>1.) p =1.; c.setHslF(c.hueF(), c.saturationF(), p); LUT[i] = c.rgba(); } //Application de la LUT sur les pixel for(int i = 0; i < img.height(); ++i) { for(int j = 0; j < img.width(); ++j) { img.setPixel(j,i, LUT[img.pixel(j,i)]); } } //Affiche la valeur complex d'initialisation utilisée. { QPainter p(&img); p.setPen(Qt::red); p.drawText(5,15,QString("{ %1 , %2 }").arg(m_z0.real()).arg(m_z0.imag())); } return QPixmap::fromImage(img); } public slots : //affiche l'image résultat reçu. void addResult(int id) { Operator::result_type res = m_watcher.future().resultAt(id); if(res.second.max > max) { max = res.second.max; } for(int i = 0; i < res.second.z.subPixelZone.height(); ++i) { for(int j = 0; j < res.second.z.subPixelZone.width(); ++j) { m_res.setPixel(res.second.z.subPixelZone.left()+j, res.second.z.subPixelZone.top()+i, res.first.pixel(j,i)); } } } //affiche l'image intermédiaire reçu. void display() { m_imgDisplay->setPixmap(imgtoPixmap(m_res, max)); } }; //convertie une image reçu en QPixmap à afficher. QPixmap imgtoPixmap(QImage img,int max) { //Creation d'une LUT pour convertir les valeurs uint des pixels de img en couleur. QColor c(Qt::blue); QVector<quint32> LUT(max+1); LUT[0] = qRgba(0,0,0,255); for(int i =1; i < LUT.size();++i) { double p = double(i) / max; if(p>1.) p =1.; c.setHslF(c.hueF(), c.saturationF(), p); LUT[i] = c.rgba(); } //Application de la LUT sur les pixel for(int i = 0; i < img.height(); ++i) { for(int j = 0; j < img.width(); ++j) { img.setPixel(j,i, LUT[img.pixel(j,i)]); } } return QPixmap::fromImage(img); } #include "main.moc" int main(int argc, char *argv[]) { //initialisation de la graine aléatoire. qsrand(QTime::currentTime().msecsTo(QTime())); QApplication a(argc, argv); maWidget w; w.show(); return a.exec(); }
Il existe encore trois petite classe qui intéressera les plus expérimenté :