OVH Cloud OVH Cloud

volatile et template

29 réponses
Avatar
Aurélien REGAT-BARREL
Bonjour,
J'ai une application multi threadée avec le thread principal qui lance un
autre thread en lui donnant en paramètre un smart pointer (c'est pas tout à
fait ça mais presque).
Je voudrais déclarer dans mon thread secondaire le smart pointer comme
volatile, mais ça marche pas (à l'utilisation):

boost::shared_ptr<int> v1( new int );
if ( v1 ) // ok
{
}

volatile boost::shared_ptr<int> v2( new int );
if ( v2 ) // erreur de compilation ici
{
}

comment m'en sortir ?
Merci.

--
Aurélien REGAT-BARREL

9 réponses

1 2 3
Avatar
kanze
Aurélien REGAT-BARREL wrote:
Volatile n'est vraissemblablement pas la solution d'un
probleme de multithread


Je dirais même plus : il me semble que volatile est surtout
utilisé sur des systèmes ne gérant pas plusieurs threads, et
sert à indiquer que la variable peut être modifiée par une
interruption. En gros, sous DOS, ça pouvait peut-être servir
; sous Windows 32 bits, guère.


Apparement ma crainte est infondée alors:

class Test
{
public:
void DoSomething()
{
if ( this->DoIt )
{
}
}

bool DoIt;
};

J'ai peur que si thread1 modifie DoIt, thread2 "loupe" cette
modification lors d'un appel à DoSomething() sur la même
instance, suite à une optimisation du compilo (mise de DoIt
dans un registre...).


C'est tout à fait possible. Avec ou sans volatile. Si DoIt peut
être modifié par un autre thread, tu as un comportement indéfini
ci-dessus. Au moins selon la norme Posix, mais tout ce que j'ai
pu voir me fait penser que c'est pareil sous Windows.

Note bien ici qu'il ne s'agit pas uniquement de l'optimisation
faite par le compilateur. Les processeurs modernes font beaucoup
d'optimisations automatiques en hardware. Alors, même si le
compilateur génère l'instruction de rélire la mémoire chaque
fois, rien ne garantit que cette rélecture va plus loin qu'un
régistre de cache interne au processeur, à moins que le
compilateur génère des instructions spéciales pour l'obliger (un
préfixe LOCK sur IA-32, une instruction membar sur Sparc, etc.).

La règle est simple : si un objet peut être modifier, et qu'il y
a plus d'un thread qui puisse y accéder, tous les accès, les
lectures aussi bien que les écritures, doivent être protégés.
Donc, par exemple :

void Test::DoSomething()
{
Lock< Mutex > l( myMutex ) ;
if ( this->DoIt ) {
ceQuilFautFaire() ;
}
}

On peut faire mieux, mais pour y arriver, il faut maîtriser
l'architecture en question et ne pas avoir peur d'un peu
d'assembleur.

Mais d'après ce que je comprends de ton problème, c'est pire.
Parce que si j'ai bien compris, ce que tu vas faire dans le if,
c'est générer un évenemment pour un autre thread et l'y envoyer.
Alors, le test qu'il te faut, c'est que DoIt soit vrai quand
l'évenemment sera traité dans l'autre thread. Et il n'y a rien
ni dans C++ ni dans Posix (et j'imagine pas dans Windows non
plus) qui permet à tester un état future.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
kanze
1GArnaud Debaene wrote:
Aurélien REGAT-BARREL wrote:
Apparement ma crainte est infondée alors:

class Test
{
public:
void DoSomething()
{
if ( this->DoIt )
{
}
}

bool DoIt;
};

J'ai peur que si thread1 modifie DoIt, thread2 "loupe" cette
modification lors d'un appel à DoSomething() sur la même
instance, suite à une optimisation du compilo (mise de DoIt
dans un registre...).


DoIt étant potentiellement accessible par un autre thread, le
compilateur n'a pas le droit de le cacher en registre.


D'où est-ce que tu tiens ça ? Posix ne l'exige pas, par exemple.
(Et évidemment, la norme C++ ne l'exige pas, puisqu'elle ne
connaît pas les threads.) Et il y a de bonnes raisons pour
donner plus de liberté au compilateur.

Note aussi que, comme j'ai dit ailleurs, il n'y a pas que le
compilateur qui peut faire ce genre d'optimisation ; la plupart
du hardware moderne en est également capable.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Aurélien REGAT-BARREL
Est-ce qu'il ne serait pas possible d'envoyer un identificateur,
et de faire le mapping identificateur->objet graphique qu'une
fois l'événement arrivé dans le thread d'affichage ?


A ma connaissance non. C'est à moi à faire le mapping éventuellement. Pour
envoyer il me faut un destinataire, qui doit être de la forme pointeur sur
une instance d'objet QObject. En fait je suppose que mon
postEvent( myobj, myevent );
est simplement transformé en
myobj->event( myevent );
sauf que l'opération est asynchrone et faire par le thread GUI.

Bon je réalise que rien n'empêche ptr d'être invalidé après le
test... Bref, je vais devoir faire de la synchronisation.


Tout à fait. Mais même. À un moment donné, tu vas décider que
c'est bon, que le pointeur n'est pas nul. Tu as déjà pris le
lock, évidemment, et tu construis donc l'événement et tout ce
qu'il faut, que tu envoies à Qt. Puis tu libère le lock.
Seulement, Qt a peut-être déjà d'autres évenemments dans sa
queue. Et qu'est-ce qui se passe si l'évenemment avant le tien
provoque la destruction de ton cible ?


Oui c'est vrai. Bon rappelons quand même que si ça se produit quoiqu'il
arrive c'est à la suite d'une erreur de programmation car le thread de
calcul doit se terminer avant que la fenêtre ne se ferme. La vérif du
pointeur nul c'est pour éviter de planter systématiquement si y'a un
probleme (c'est toujours possible mais faut quand même pas mal d'essais)
afin de pouvoir faire un petit test et de logger un message d'erreur. A la
limite ce serait presque mieux que ça plante car souvent ce n'est pas
seulement un thread de calcul mais un thread qui pilote un appareil branché
au PC etc... Si le thread n'est pas contrôlé, il est à craindre qu'un
deuxième soit lancé et là ça va faire mal. Je vais poser un assert() et
rajouter 2 lignes de commentaires en majuscules.

Idéalement, c'est à peu près comme ça que je travaillerais. Sauf
que le fenêtre ne se fermera réelement que sur la commande du
thread de calcul -- la commande de fermature de la fenêtre se
convertit donc en commande d'arrêter le calcul, et l'arrêt du
calcul provoque l'envoie d'une fermature de la fenêtre.


Dans ce que j'ai fait le thread n'envoie aucune commande, mais seulement des
notifications (j'en suis là, j'ai fini, etc...). Seule la fenêtre peut
envoyer un ordre au thread pour qu'il s'arrête. J'ai la possibilité de
savoir le temps nécessaire pour que théoriquement cet ordre soit pris en
compte. Donc si la fenêtre est fermée, elle envoie l'ordre d'arrêt, attend
un petit peu plus que ce qu'il faut pour que le thread se termine (+ message
"annulation en cours..."), et s'il est pas fini elle le tue et log un
message d'erreur.
J'aime pas trop être dépendant d'un thread, si jamais il est buggé
l'application reste figée à attendre que le thread ait fini et on peut plus
rien faire (comme on voit sur certaines applis des fois "attente de fin de
traitement..." sauf que plus rien ne se passe). Je préfère prévoir un
protocole style watchdog. Je sais que tu fais une opération qui dure 2
secondes. Si au bout de 3 secondes t'as pas pris en compte ma demande
d'arrêt alors c'est l'échafaud. Dans mon cas c'est possible car je connais
le temps d'exécution d'une opération du thread à 10 ms près.

--
Aurélien REGAT-BARREL


Avatar
Aurélien REGAT-BARREL
Mais d'après ce que je comprends de ton problème, c'est pire.
Parce que si j'ai bien compris, ce que tu vas faire dans le if,
c'est générer un évenemment pour un autre thread et l'y envoyer.
Alors, le test qu'il te faut, c'est que DoIt soit vrai quand
l'évenemment sera traité dans l'autre thread. Et il n'y a rien
ni dans C++ ni dans Posix (et j'imagine pas dans Windows non
plus) qui permet à tester un état future.


Oui je comprends. Imposer que le thread de calcul soit terminé avant la
fermeture de la fenêtre est donc la solution la plus simple. Plutot que de
blinder le programme si ce n'est pas respecté, je vais au contraire poser
des assertions et des tests pour stopper de suite et signaler l'erreur.
Merci.

--
Aurélien REGAT-BARREL

Avatar
Olivier Azeau
Aurélien REGAT-BARREL wrote:
Est-ce qu'il ne serait pas possible d'envoyer un identificateur,
et de faire le mapping identificateur->objet graphique qu'une
fois l'événement arrivé dans le thread d'affichage ?


A ma connaissance non. C'est à moi à faire le mapping
éventuellement. Pour

envoyer il me faut un destinataire, qui doit être de la forme
pointeur sur

une instance d'objet QObject. En fait je suppose que mon
postEvent( myobj, myevent );
est simplement transformé en
myobj->event( myevent );
sauf que l'opération est asynchrone et faire par le thread GUI.


Et avec QObject caché et toujours présent qui joue le role de mapper
vers qui sont postés tous ces evenements (charge a lui de les
dispatcher sur les objets reellement présents), ce n'est pas possible
?

[...]

Dans mon cas c'est possible car je connais
le temps d'exécution d'une opération du thread à 10 ms près.


wow !


Avatar
Aurélien REGAT-BARREL
Et avec QObject caché et toujours présent qui joue le role de mapper
vers qui sont postés tous ces evenements (charge a lui de les
dispatcher sur les objets reellement présents), ce n'est pas possible
?


Pas bête. Ca déporte le probleme vers ce nvx QObject qui doit toujours
envoyer les events vers un autre QObject qui peut toujours être détruit.
Sauf que comme les 2 sont dans le même thread, et que je peux cette fois
faire un envoi synchrone (car dans le même thread) ça devrait être plus
fiable. Reste à créer ce nouveau QObject et à gérer sa durée de vie. Mais je
me demande : mon thread est un lui même un QObject, et seule sa méthode
run() s'exécute dans le contexte d'un nouveau thread. Si je fais, dans
run(), un postEvent vers le QThread lui même, je pense que QThread::event()
sera appelé dans le contexte du thread principal GUI : il ferait alors un
envoi sécurisé. Y'a le micro risque que le QThread soit détruit (tué) juste
après un postEvent, auquel ca l'event qu'il s'est auto envoyé en asynchrone
va faire boum juste après. Mais bon, ça a l'air pas mal. Je testerai ça
prochainement.
Merci pour la suggestion.

Dans mon cas c'est possible car je connais
le temps d'exécution d'une opération du thread à 10 ms près.


wow !


Rien d'extraordinaire dans mon cas. Mon "thread de calcul" est en fait un
thread de pilotage qui effectue une série d'opérations blocantes (c'est
parce qu'elles sont blocantes que je les ai déplacées dans un autre thread).
A savoir il s'agit de faire des mesures sur un appareil : le temps de mesure
est réglable à 1 ms près. Quand je démarre une mesure, je sais donc pendant
combien de ms je vais être bloqué, aux aléas des systèmes multitâches près.

--
Aurélien REGAT-BARREL


Avatar
kanze
Aurélien REGAT-BARREL wrote:
Est-ce qu'il ne serait pas possible d'envoyer un
identificateur, et de faire le mapping identificateur->objet
graphique qu'une fois l'événement arrivé dans le thread
d'affichage ?


A ma connaissance non. C'est à moi à faire le mapping
éventuellement. Pour envoyer il me faut un destinataire, qui
doit être de la forme pointeur sur une instance d'objet
QObject. En fait je suppose que mon
postEvent( myobj, myevent );
est simplement transformé en
myobj->event( myevent );
sauf que l'opération est asynchrone et faire par le thread GUI.


Tiens. Ça me semble un peu primitif, à la C, prèsque. J'aurais
cru que la solution « naturelle » en C++, c'est que le
message/évenemment est un objet, avec une fonction virtuelle
qu'on appelle dans le traitement de l'évenemment dans le thread
GUI. Au dessus, il y aurait évidemment des messages tout faits
qui se renvoie à un objet du GUI (le QObject), mais je ne vois
pas de raison de ne pas donner accès au niveau plus bas si
l'utilisateur en a besoin.

Bon je réalise que rien n'empêche ptr d'être invalidé après
le test... Bref, je vais devoir faire de la
synchronisation.


Tout à fait. Mais même. À un moment donné, tu vas décider
que c'est bon, que le pointeur n'est pas nul. Tu as déjà
pris le lock, évidemment, et tu construis donc l'événement
et tout ce qu'il faut, que tu envoies à Qt. Puis tu libère
le lock. Seulement, Qt a peut-être déjà d'autres évenemments
dans sa queue. Et qu'est-ce qui se passe si l'évenemment
avant le tien provoque la destruction de ton cible ?


Oui c'est vrai. Bon rappelons quand même que si ça se produit
quoiqu'il arrive c'est à la suite d'une erreur de
programmation car le thread de calcul doit se terminer avant
que la fenêtre ne se ferme. La vérif du pointeur nul c'est
pour éviter de planter systématiquement si y'a un probleme
(c'est toujours possible mais faut quand même pas mal
d'essais) afin de pouvoir faire un petit test et de logger un
message d'erreur. A la limite ce serait presque mieux que ça
plante car souvent ce n'est pas seulement un thread de calcul
mais un thread qui pilote un appareil branché au PC etc... Si
le thread n'est pas contrôlé, il est à craindre qu'un deuxième
soit lancé et là ça va faire mal. Je vais poser un assert() et
rajouter 2 lignes de commentaires en majuscules.


Je ne l'avais pas compris comme ça. C'est donc que quand je
m'appuies sur le bouton pour fermer la fenêtre, on envoie en
fait un message au thread de calcul de s'arrêter, et que c'est
en s'arrêtant que le thread de calcul envoie un message à la
fenêtre de se fermer.

C'est la conception idéale ; j'avais cru comprendre que tu ne
pouvais pas intercepter l'évenemment de fermature de la fenêtre,
mais si tu peux, c'est de loin la solution la plus propre.

Idéalement, c'est à peu près comme ça que je travaillerais.
Sauf que le fenêtre ne se fermera réelement que sur la
commande du thread de calcul -- la commande de fermature de
la fenêtre se convertit donc en commande d'arrêter le
calcul, et l'arrêt du calcul provoque l'envoie d'une
fermature de la fenêtre.


Dans ce que j'ai fait le thread n'envoie aucune commande, mais
seulement des notifications (j'en suis là, j'ai fini, etc...).


Mais la fenêtre a un comportement défini vis-à-vis de ces
notifications, n'est-ce pas ? (Je suis d'accord que c'est mieux
que ce soit la fenêtre qui définit le comportement, et que les
messages se présente comme des notifications de changement
d'état.)

Seule la fenêtre peut envoyer un ordre au thread pour qu'il
s'arrête. J'ai la possibilité de savoir le temps nécessaire
pour que théoriquement cet ordre soit pris en compte. Donc si
la fenêtre est fermée, elle envoie l'ordre d'arrêt, attend un
petit peu plus que ce qu'il faut pour que le thread se termine
(+ message "annulation en cours..."), et s'il est pas fini
elle le tue et log un message d'erreur.


Ça me semble très propre. Où en est le problème ?

J'aime pas trop être dépendant d'un thread, si jamais il est
buggé l'application reste figée à attendre que le thread ait
fini et on peut plus rien faire (comme on voit sur certaines
applis des fois "attente de fin de traitement..." sauf que
plus rien ne se passe).


C-à-d que ton problème, c'est que tu veux pouvoir fermer la
fenêtre même si le thread de calcul ne réagit pas à la commande
de s'arrêter.

Dans ce cas-là, je ne vois pas trop de possibilité d'éviter une
condition de race -- si jamais le thread de calcul décide
finalement de s'arrêter ou d'envoyer une notification quelconque
juste au moment que tu fais la décision qu'on ne peut plus
attendre, et qu'il faut fermer la fenêtre, il va y avoir un
problème.

Mais je crois que si le thread de calcul ne réagit pas
correctement à la commande de s'arrêter, tu as de toute façon un
problème. S'il continue indéfiniment, il bouffe des ressources,
qui ne seront pas libérées. Éventuellement, la raison qu'il ne
s'arrête pas est lié à un problème ailleurs, un pointeur mal
initialisé qui a bousillé de la mémoire là où il ne devait pas,
par exemple. En fait, tu ne sais plus grand chose de l'état de
ton programme. Alors, est-ce qu'il faut faire semblant et
continuer comme si rien ne s'est passé, ou est-ce qu'il ne vaut
pas mieux de s'arrêter brutalement avec un message d'erreur et
un post-mortem (le core dump des Unixistes -- je ne sais pas
comment il s'appelle sous Windows).

Je préfère prévoir un protocole style watchdog. Je sais que tu
fais une opération qui dure 2 secondes. Si au bout de 3
secondes t'as pas pris en compte ma demande d'arrêt alors
c'est l'échafaud.


C'est bien, mais il faut pouvoir tuer l'autre pour qu'il soit
réelement efficace. Donc, tu attends n secondes, puis tu tues
l'autre thread de façon brutale. Sauf que tous les systèmes
d'exploitation n'ont pas cette possibilité ; sous Unix, si un
thread ne veut pas être tué, et qu'il prend les bonnes
précautions, il n'y a aucun moyen de le tuer sans tuer tout le
processus.

Évidemment, si tu veux vraiment être robuste, il faudrait
utiliser un processus à part pour le calcul, et non simplement
un thread. (En fait, c'est une solution souvent négligée.)

Dans mon cas c'est possible car je connais le temps
d'exécution d'une opération du thread à 10 ms près.


En supposant qu'il n'y a pas d'autre charge sur le CPU,
évidemment.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



Avatar
Aurélien REGAT-BARREL
Tiens. Ça me semble un peu primitif, à la C, prèsque. J'aurais
cru que la solution « naturelle » en C++, c'est que le
message/évenemment est un objet,


C'est le cas, mais c'est un objet qui sert juste à trimballer des
paramètres. En fait y'a un objet de base QEvent avec une fonction type() qui
permet de faire du rtti maison. Pour chaque type d'event existant on dérive
de QEvent avec son propre id renvoyé par type(), et ensuite c'est du
downcasting en fonction de cette id.

avec une fonction virtuelle
qu'on appelle dans le traitement de l'évenemment dans le thread
GUI.


Que ferait cette fonction virtuelle ?

Au dessus, il y aurait évidemment des messages tout faits
qui se renvoie à un objet du GUI (le QObject), mais je ne vois
pas de raison de ne pas donner accès au niveau plus bas si
l'utilisateur en a besoin.


Il existe tout un tas de QEvent prédéfinis (tel que QCloseEvent() qui permet
d'intercepter la fermeture de ma fenêtre). Pour envoyer un event il faut
préciser le QObject destinataire. On s'adresse au "moteur" Qt pour envoyer
un event -> l'objet singleton application : QApplication::sendEvent(
receiver, event ), ou postEvent() en asynchrone.

Je ne l'avais pas compris comme ça. C'est donc que quand je
m'appuies sur le bouton pour fermer la fenêtre, on envoie en
fait un message au thread de calcul de s'arrêter, et que c'est
en s'arrêtant que le thread de calcul envoie un message à la
fenêtre de se fermer.


Oui.

Dans ce que j'ai fait le thread n'envoie aucune commande, mais
seulement des notifications (j'en suis là, j'ai fini, etc...).


Mais la fenêtre a un comportement défini vis-à-vis de ces
notifications, n'est-ce pas ? (Je suis d'accord que c'est mieux
que ce soit la fenêtre qui définit le comportement, et que les
messages se présente comme des notifications de changement
d'état.)


Oui, elle lance le thread et indique l'avancement de ce dernier en fonction
des notifications (event) reçus.

Seule la fenêtre peut envoyer un ordre au thread pour qu'il
s'arrête. J'ai la possibilité de savoir le temps nécessaire
pour que théoriquement cet ordre soit pris en compte. Donc si
la fenêtre est fermée, elle envoie l'ordre d'arrêt, attend un
petit peu plus que ce qu'il faut pour que le thread se termine
(+ message "annulation en cours..."), et s'il est pas fini
elle le tue et log un message d'erreur.


Ça me semble très propre. Où en est le problème ?


Dans ce cas il n'y en a pas. C'est juste si plus tard (ce principe va être
réutilisé ailleurs) le programmeur oublie d'intercepter la fermeture de la
fenêtre alors que le thread n'est pas terminé, hop ça plante (envoie d'un
event vers une fenêtre détruite). Si c'est correctement programmé pas de
soucis.

J'aime pas trop être dépendant d'un thread, si jamais il est
buggé l'application reste figée à attendre que le thread ait
fini et on peut plus rien faire (comme on voit sur certaines
applis des fois "attente de fin de traitement..." sauf que
plus rien ne se passe).


C-à-d que ton problème, c'est que tu veux pouvoir fermer la
fenêtre même si le thread de calcul ne réagit pas à la commande
de s'arrêter.


Oui. Mais c'est pas un problème. Je lui laisse un délais largement suffisant
pour qu'il se termine, et s'il ne le fait pas, je le tue. Mon thread pilote
une machine, à priori il ne devrait jamais se bloquer plus longtemps que ce
qui est prévu. Mais on est pas à l'abrit d'un problème avec le SDK utilisé
pour le pilotage (comme c'est le cas sur un PC où un problème apparement
hardware fait que le SDK ne répond plus si utilisé d'une certaine manière).

Dans ce cas-là, je ne vois pas trop de possibilité d'éviter une
condition de race -- si jamais le thread de calcul décide
finalement de s'arrêter ou d'envoyer une notification quelconque
juste au moment que tu fais la décision qu'on ne peut plus
attendre, et qu'il faut fermer la fenêtre, il va y avoir un
problème.


Pas dans mon cas car une fois que j'ai décidé qu'on ne pouvait plus
attendre, le thread est tué, puis la fenêtre fermée.

Mais je crois que si le thread de calcul ne réagit pas
correctement à la commande de s'arrêter, tu as de toute façon un
problème. S'il continue indéfiniment, il bouffe des ressources,
qui ne seront pas libérées. Éventuellement, la raison qu'il ne
s'arrête pas est lié à un problème ailleurs, un pointeur mal
initialisé qui a bousillé de la mémoire là où il ne devait pas,
par exemple. En fait, tu ne sais plus grand chose de l'état de
ton programme. Alors, est-ce qu'il faut faire semblant et
continuer comme si rien ne s'est passé, ou est-ce qu'il ne vaut
pas mieux de s'arrêter brutalement avec un message d'erreur et
un post-mortem (le core dump des Unixistes -- je ne sais pas
comment il s'appelle sous Windows).


Dans mon cas l'erreur serait qu'il serait impossible de piloter la machine
branchée au PC. Ca ne doit pas vautrer tout le programme pour autant. Mais
c'est clair que l'erreur doit être signalée et loggée, et c'est le cas.

Je préfère prévoir un protocole style watchdog. Je sais que tu
fais une opération qui dure 2 secondes. Si au bout de 3
secondes t'as pas pris en compte ma demande d'arrêt alors
c'est l'échafaud.


C'est bien, mais il faut pouvoir tuer l'autre pour qu'il soit
réelement efficace. Donc, tu attends n secondes, puis tu tues
l'autre thread de façon brutale. Sauf que tous les systèmes
d'exploitation n'ont pas cette possibilité ; sous Unix, si un
thread ne veut pas être tué, et qu'il prend les bonnes
précautions, il n'y a aucun moyen de le tuer sans tuer tout le
processus.


Ca c'est le problème de Qt ;-)
La doc dit que le kill peut ne pas être exécuté de suite, mais à priori il
est toujours effectué. Vu que je vais pas m'amuser à modifier mon propre
thread afin de le rendre immortel, ça me suffit.

--
Aurélien REGAT-BARREL


Avatar
kanze
Aurélien REGAT-BARREL wrote:
Tiens. Ça me semble un peu primitif, à la C, prèsque.
J'aurais cru que la solution « naturelle » en C++, c'est que
le message/évenemment est un objet,


C'est le cas, mais c'est un objet qui sert juste à trimballer
des paramètres. En fait y'a un objet de base QEvent avec une
fonction type() qui permet de faire du rtti maison. Pour
chaque type d'event existant on dérive de QEvent avec son
propre id renvoyé par type(), et ensuite c'est du downcasting
en fonction de cette id.

avec une fonction virtuelle qu'on appelle dans le traitement
de l'évenemment dans le thread GUI.


Que ferait cette fonction virtuelle ?


Ce qu'on veut. Si tu veux, à la place d'un système d'envoi
d'évenemment, on a simplement un mechanisme qui permet d'appeler
une fonction quelconque (avec des données associées) dans le
thread en question. L'envoi des évenemments est bâti dessus.

Au dessus, il y aurait évidemment des messages tout faits
qui se renvoie à un objet du GUI (le QObject), mais je ne
vois pas de raison de ne pas donner accès au niveau plus bas
si l'utilisateur en a besoin.


Il existe tout un tas de QEvent prédéfinis (tel que
QCloseEvent() qui permet d'intercepter la fermeture de ma
fenêtre). Pour envoyer un event il faut préciser le QObject
destinataire. On s'adresse au "moteur" Qt pour envoyer un
event -> l'objet singleton application :
QApplication::sendEvent( receiver, event ), ou postEvent() en
asynchrone.

Je ne l'avais pas compris comme ça. C'est donc que quand je
m'appuies sur le bouton pour fermer la fenêtre, on envoie en
fait un message au thread de calcul de s'arrêter, et que
c'est en s'arrêtant que le thread de calcul envoie un
message à la fenêtre de se fermer.


Oui.

Dans ce que j'ai fait le thread n'envoie aucune commande,
mais seulement des notifications (j'en suis là, j'ai fini,
etc...).


Mais la fenêtre a un comportement défini vis-à-vis de ces
notifications, n'est-ce pas ? (Je suis d'accord que c'est
mieux que ce soit la fenêtre qui définit le comportement, et
que les messages se présente comme des notifications de
changement d'état.)


Oui, elle lance le thread et indique l'avancement de ce
dernier en fonction des notifications (event) reçus.

Seule la fenêtre peut envoyer un ordre au thread pour qu'il
s'arrête. J'ai la possibilité de savoir le temps nécessaire
pour que théoriquement cet ordre soit pris en compte. Donc
si la fenêtre est fermée, elle envoie l'ordre d'arrêt,
attend un petit peu plus que ce qu'il faut pour que le
thread se termine (+ message "annulation en cours..."), et
s'il est pas fini elle le tue et log un message d'erreur.


Ça me semble très propre. Où en est le problème ?


Dans ce cas il n'y en a pas. C'est juste si plus tard (ce
principe va être réutilisé ailleurs) le programmeur oublie
d'intercepter la fermeture de la fenêtre alors que le thread
n'est pas terminé, hop ça plante (envoie d'un event vers une
fenêtre détruite). Si c'est correctement programmé pas de
soucis.


Si le programmeur oublit ce qu'il faut, il va y avoir des
problèmes, c'est sûr. Je comprends ton souci ; je crois moi
aussi à la programmation défensive. Mais il y a des limites de
ce qu'on peut faire.

J'aime pas trop être dépendant d'un thread, si jamais il
est buggé l'application reste figée à attendre que le
thread ait fini et on peut plus rien faire (comme on voit
sur certaines applis des fois "attente de fin de
traitement..." sauf que plus rien ne se passe).


C-à-d que ton problème, c'est que tu veux pouvoir fermer la
fenêtre même si le thread de calcul ne réagit pas à la
commande de s'arrêter.


Oui. Mais c'est pas un problème. Je lui laisse un délais
largement suffisant pour qu'il se termine, et s'il ne le fait
pas, je le tue. Mon thread pilote une machine, à priori il ne
devrait jamais se bloquer plus longtemps que ce qui est prévu.
Mais on est pas à l'abrit d'un problème avec le SDK utilisé
pour le pilotage (comme c'est le cas sur un PC où un problème
apparement hardware fait que le SDK ne répond plus si utilisé
d'une certaine manière).

Dans ce cas-là, je ne vois pas trop de possibilité d'éviter
une condition de race -- si jamais le thread de calcul
décide finalement de s'arrêter ou d'envoyer une notification
quelconque juste au moment que tu fais la décision qu'on ne
peut plus attendre, et qu'il faut fermer la fenêtre, il va y
avoir un problème.


Pas dans mon cas car une fois que j'ai décidé qu'on ne pouvait
plus attendre, le thread est tué, puis la fenêtre fermée.


Si on peut bien tuer le thread, pas de problème. Ce n'est pas le
cas dans tous les systèmes. (En Posix, par exemple, il n'y a
aucune façon garantie de tuer un thread sans tuer le processus.)

Mais je crois que si le thread de calcul ne réagit pas
correctement à la commande de s'arrêter, tu as de toute
façon un problème. S'il continue indéfiniment, il bouffe des
ressources, qui ne seront pas libérées. Éventuellement, la
raison qu'il ne s'arrête pas est lié à un problème ailleurs,
un pointeur mal initialisé qui a bousillé de la mémoire là
où il ne devait pas, par exemple. En fait, tu ne sais plus
grand chose de l'état de ton programme. Alors, est-ce qu'il
faut faire semblant et continuer comme si rien ne s'est
passé, ou est-ce qu'il ne vaut pas mieux de s'arrêter
brutalement avec un message d'erreur et un post-mortem (le
core dump des Unixistes -- je ne sais pas comment il
s'appelle sous Windows).


Dans mon cas l'erreur serait qu'il serait impossible de
piloter la machine branchée au PC. Ca ne doit pas vautrer tout
le programme pour autant. Mais c'est clair que l'erreur doit
être signalée et loggée, et c'est le cas.

Je préfère prévoir un protocole style watchdog. Je sais que
tu fais une opération qui dure 2 secondes. Si au bout de 3
secondes t'as pas pris en compte ma demande d'arrêt alors
c'est l'échafaud.


C'est bien, mais il faut pouvoir tuer l'autre pour qu'il
soit réelement efficace. Donc, tu attends n secondes, puis
tu tues l'autre thread de façon brutale. Sauf que tous les
systèmes d'exploitation n'ont pas cette possibilité ; sous
Unix, si un thread ne veut pas être tué, et qu'il prend les
bonnes précautions, il n'y a aucun moyen de le tuer sans
tuer tout le processus.


Ca c'est le problème de Qt ;-)

La doc dit que le kill peut ne pas être exécuté de suite, mais
à priori il est toujours effectué. Vu que je vais pas m'amuser
à modifier mon propre thread afin de le rendre immortel, ça me
suffit.


Si c'est Qt qui gère les threads, il peut le garantir ; Java le
garantit, et Java tourne sur Unix. En C++, en revanche, je ne
crois pas que Qt puisse effectivement t'interdire l'accès au bas
niveau, ce qui te permet par exemple de masquer des
pthread_cancel complètement.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



1 2 3