Dans son cas, la solution serait sans doute celle imposée par
Qt, que je connais pas. Dans les systèmes de fenêtrage que je
connais, toutes les mises à jours des graphiques se font dans un
thread particulier, qui est aussi celui que gère les
évenemments. Pour mettre à jour un barre d'avancement, par
exemple, on envoie un évenement au thread gestionnaire des
événements, qui s'en occupe. L'alternatif, c'est (en supposant
un MVC) que le thread de calcul fasse la mise à jour du modèle,
puis envoie un événemment au thread d'affichage pour l'informer
qu'il y a eu un changement.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Et ce n'est pas évident pour le
faire marcher correctement -- en gros, je dirais que
l'utilisation du pointeur en tant que pointeur ne doit avoir
lieu que dans le thread d'affichage, qui doit aussi être le
seul thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui gère la
destruction ; il peuvent donc bien s'arranger pour que la
destruction n'a pas lieu à un moment importun. Ici, c'est Qt
qui le gère, et il faut être certain que Qt ne le fait pas
quand on a déjà extrait le pointeur brut, mais qu'on n'en a
pas encore servi. AMHA, la solution qui s'impose, c'est que
le seul thread qui se sert du pointeur brut, c'est le même
thread que celui qui pourrait éventuellement faire un delete
sur l'objet.
2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Dans le deuxième cas, il faut bien que le thread de calcul ait
accès à l'objet d'affichage.
Dans ce cas-là, il faut
impérativement 1) qu'il soit observeur, pour que le pointeur
soit mis à null lors d'un delete, et 2) qu'il detient un lock
aussi longtemps qu'il a une copie du pointeur, et que ce même
lock protège la deletion (c-à-d qu'autant qu'il a le lock, on ne
peut pas deleter).
Pour 2), j'aime plutôt l'idée d'un pointeur
intelligent dont le constructeur aquiert le lock, et le dernier
destructeur le libère. On peut se servir de boost::smart_ptr
ici, à condition d'acquérir le lock avant de créer le pointeur,
et que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère le
lock (et qui ne delete rien). Évidemment, il faut que le code
qui effectue le delete de l'objet prend le même lock. (Je suis
assez scéptique sur la prise d'un lock dans un destructeur. Mais
à la rigueur, comme première action dans le destructeur de la
classe la plus dérivée. Ce qu'il faut tenir en compte, c'est
qu'entre le moment que la décision a été prise de detruire
l'objet et le moment que le thread d'affichage prend le lock, le
thread de calcul peut interrompre, obtenir un pointeur à l'objet
d'affichage, et s'en servir. Or, si on est déjà dans le
destructeur...)
Dans son cas, la solution serait sans doute celle imposée par
Qt, que je connais pas. Dans les systèmes de fenêtrage que je
connais, toutes les mises à jours des graphiques se font dans un
thread particulier, qui est aussi celui que gère les
évenemments. Pour mettre à jour un barre d'avancement, par
exemple, on envoie un évenement au thread gestionnaire des
événements, qui s'en occupe. L'alternatif, c'est (en supposant
un MVC) que le thread de calcul fasse la mise à jour du modèle,
puis envoie un événemment au thread d'affichage pour l'informer
qu'il y a eu un changement.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Et ce n'est pas évident pour le
faire marcher correctement -- en gros, je dirais que
l'utilisation du pointeur en tant que pointeur ne doit avoir
lieu que dans le thread d'affichage, qui doit aussi être le
seul thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui gère la
destruction ; il peuvent donc bien s'arranger pour que la
destruction n'a pas lieu à un moment importun. Ici, c'est Qt
qui le gère, et il faut être certain que Qt ne le fait pas
quand on a déjà extrait le pointeur brut, mais qu'on n'en a
pas encore servi. AMHA, la solution qui s'impose, c'est que
le seul thread qui se sert du pointeur brut, c'est le même
thread que celui qui pourrait éventuellement faire un delete
sur l'objet.
2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Dans le deuxième cas, il faut bien que le thread de calcul ait
accès à l'objet d'affichage.
Dans ce cas-là, il faut
impérativement 1) qu'il soit observeur, pour que le pointeur
soit mis à null lors d'un delete, et 2) qu'il detient un lock
aussi longtemps qu'il a une copie du pointeur, et que ce même
lock protège la deletion (c-à-d qu'autant qu'il a le lock, on ne
peut pas deleter).
Pour 2), j'aime plutôt l'idée d'un pointeur
intelligent dont le constructeur aquiert le lock, et le dernier
destructeur le libère. On peut se servir de boost::smart_ptr
ici, à condition d'acquérir le lock avant de créer le pointeur,
et que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère le
lock (et qui ne delete rien). Évidemment, il faut que le code
qui effectue le delete de l'objet prend le même lock. (Je suis
assez scéptique sur la prise d'un lock dans un destructeur. Mais
à la rigueur, comme première action dans le destructeur de la
classe la plus dérivée. Ce qu'il faut tenir en compte, c'est
qu'entre le moment que la décision a été prise de detruire
l'objet et le moment que le thread d'affichage prend le lock, le
thread de calcul peut interrompre, obtenir un pointeur à l'objet
d'affichage, et s'en servir. Or, si on est déjà dans le
destructeur...)
Dans son cas, la solution serait sans doute celle imposée par
Qt, que je connais pas. Dans les systèmes de fenêtrage que je
connais, toutes les mises à jours des graphiques se font dans un
thread particulier, qui est aussi celui que gère les
évenemments. Pour mettre à jour un barre d'avancement, par
exemple, on envoie un évenement au thread gestionnaire des
événements, qui s'en occupe. L'alternatif, c'est (en supposant
un MVC) que le thread de calcul fasse la mise à jour du modèle,
puis envoie un événemment au thread d'affichage pour l'informer
qu'il y a eu un changement.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Et ce n'est pas évident pour le
faire marcher correctement -- en gros, je dirais que
l'utilisation du pointeur en tant que pointeur ne doit avoir
lieu que dans le thread d'affichage, qui doit aussi être le
seul thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui gère la
destruction ; il peuvent donc bien s'arranger pour que la
destruction n'a pas lieu à un moment importun. Ici, c'est Qt
qui le gère, et il faut être certain que Qt ne le fait pas
quand on a déjà extrait le pointeur brut, mais qu'on n'en a
pas encore servi. AMHA, la solution qui s'impose, c'est que
le seul thread qui se sert du pointeur brut, c'est le même
thread que celui qui pourrait éventuellement faire un delete
sur l'objet.
2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Dans le deuxième cas, il faut bien que le thread de calcul ait
accès à l'objet d'affichage.
Dans ce cas-là, il faut
impérativement 1) qu'il soit observeur, pour que le pointeur
soit mis à null lors d'un delete, et 2) qu'il detient un lock
aussi longtemps qu'il a une copie du pointeur, et que ce même
lock protège la deletion (c-à-d qu'autant qu'il a le lock, on ne
peut pas deleter).
Pour 2), j'aime plutôt l'idée d'un pointeur
intelligent dont le constructeur aquiert le lock, et le dernier
destructeur le libère. On peut se servir de boost::smart_ptr
ici, à condition d'acquérir le lock avant de créer le pointeur,
et que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère le
lock (et qui ne delete rien). Évidemment, il faut que le code
qui effectue le delete de l'objet prend le même lock. (Je suis
assez scéptique sur la prise d'un lock dans un destructeur. Mais
à la rigueur, comme première action dans le destructeur de la
classe la plus dérivée. Ce qu'il faut tenir en compte, c'est
qu'entre le moment que la décision a été prise de detruire
l'objet et le moment que le thread d'affichage prend le lock, le
thread de calcul peut interrompre, obtenir un pointeur à l'objet
d'affichage, et s'en servir. Or, si on est déjà dans le
destructeur...)
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
fenetre-> MiseAJourAffichage();
http://perso.edulang.com/fclc++/critical_section.h.zip
fenetre-> MiseAJourAffichage();
http://perso.edulang.com/fclc++/critical_section.h.zip
fenetre-> MiseAJourAffichage();
http://perso.edulang.com/fclc++/critical_section.h.zip
La question qui est derriere cela c'est : le systeme de fenetrage
est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce systeme dans un
seul et meme thread : meme si la fenetre est toujours présente et que
le thread de calcul y fait un appel, rien ne dit qu'un autre thread de
calcul qui fait, au meme moment, un appel a une autre fenetre ne va pas
causer un probleme au sein du systeme de fenetrage.
La question qui est derriere cela c'est : le systeme de fenetrage
est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce systeme dans un
seul et meme thread : meme si la fenetre est toujours présente et que
le thread de calcul y fait un appel, rien ne dit qu'un autre thread de
calcul qui fait, au meme moment, un appel a une autre fenetre ne va pas
causer un probleme au sein du systeme de fenetrage.
La question qui est derriere cela c'est : le systeme de fenetrage
est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce systeme dans un
seul et meme thread : meme si la fenetre est toujours présente et que
le thread de calcul y fait un appel, rien ne dit qu'un autre thread de
calcul qui fait, au meme moment, un appel a une autre fenetre ne va pas
causer un probleme au sein du systeme de fenetrage.
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.
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.
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.
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
A quoi sert volatile alors ?
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
A quoi sert volatile alors ?
Volatile ne regle aucun probleme de visibilite de modification entre
threads. A ma connaissance aucun compilateur n'introduit ce qui est
necessaire automatiquement. Ce qu'il faut depend du systeme et je ne
connais pas Windows.
A quoi sert volatile alors ?
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...).
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...).
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. Il peut le faire par contre
pour
une variable locale car on est certain qu'elle n'est accédée par personne
d'autre.
DoIt étant potentiellement accessible par un autre thread, le compilateur
n'a pas le droit de le cacher en registre. Il peut le faire par contre
pour
une variable locale car on est certain qu'elle n'est accédée par personne
d'autre.
DoIt étant potentiellement accessible par un autre thread, le compilateur
n'a pas le droit de le cacher en registre. Il peut le faire par contre
pour
une variable locale car on est certain qu'elle n'est accédée par personne
d'autre.
wrote:Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments.
La question qui est derriere cela c'est : le systeme de
fenetrage est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce
systeme dans un seul et meme thread : meme si la fenetre est
toujours présente et que le thread de calcul y fait un appel,
rien ne dit qu'un autre thread de calcul qui fait, au meme
moment, un appel a une autre fenetre ne va pas causer un
probleme au sein du systeme de fenetrage.
Bien souvent (en tt cas dans les applis que je connais), quand
on a besoin d'integrer un composant tiers, systeme de
fenetrage ou autre, on cantonne tous les appels dans un seul
thread pour ne pas risquer ce genre de mésaventure.
Je suppose qu'il y a un nom (que je ne connais pas) a cet
idiome.
kanze@gabi-soft.fr wrote:
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments.
La question qui est derriere cela c'est : le systeme de
fenetrage est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce
systeme dans un seul et meme thread : meme si la fenetre est
toujours présente et que le thread de calcul y fait un appel,
rien ne dit qu'un autre thread de calcul qui fait, au meme
moment, un appel a une autre fenetre ne va pas causer un
probleme au sein du systeme de fenetrage.
Bien souvent (en tt cas dans les applis que je connais), quand
on a besoin d'integrer un composant tiers, systeme de
fenetrage ou autre, on cantonne tous les appels dans un seul
thread pour ne pas risquer ce genre de mésaventure.
Je suppose qu'il y a un nom (que je ne connais pas) a cet
idiome.
wrote:Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments.
La question qui est derriere cela c'est : le systeme de
fenetrage est-il thread-safe ?
Dans le doute, il vaut mieux faire tous les appels a ce
systeme dans un seul et meme thread : meme si la fenetre est
toujours présente et que le thread de calcul y fait un appel,
rien ne dit qu'un autre thread de calcul qui fait, au meme
moment, un appel a une autre fenetre ne va pas causer un
probleme au sein du systeme de fenetrage.
Bien souvent (en tt cas dans les applis que je connais), quand
on a besoin d'integrer un composant tiers, systeme de
fenetrage ou autre, on cantonne tous les appels dans un seul
thread pour ne pas risquer ce genre de mésaventure.
Je suppose qu'il y a un nom (que je ne connais pas) a cet
idiome.
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments. Pour mettre à jour un barre
d'avancement, par exemple, on envoie un évenement au thread
gestionnaire des événements, qui s'en occupe. L'alternatif,
c'est (en supposant un MVC) que le thread de calcul fasse la
mise à jour du modèle, puis envoie un événemment au thread
d'affichage pour l'informer qu'il y a eu un changement.
C'est exactement cela. Le probleme est dans l'envoie de
l'évènement. Pour cela, je dois spécifier la fenêtre
destinatrice. Mon probleme : si par la suite cette fenêtre est
détruite, l'envoie d'évènement fait tout crasher (pointeur
null reference).
Qt aurait pu être mieux fouttu sur ce point mais c'est pas le
cas. Alors j'ai pris mes précautions, ça ne se produira plus,
mais bon j'aime pas avoir ce genre de sournoiserie qui
traine...
Ce qui m'est imposé par Qt, c'est d'utiliser un QObject* comme
destinataire de l'évènement. Le probleme c'est que si ce
QObject* est détruit par la suite, boum badaboum.
Solution : j'utilise un QGuardedPtr<QObject>, qui a une
particularité : être automatiquement mis à NULL si l'objet
pointé est détruit. Ca marche très bien.
Ma question : le fait que ce QGuardedPtr soit mis à NULL par
un autre thread, je me demande si une optimisation du compilo
ne risque pas de lui faire louper l'info.
C'est un peu comme si on avait ça:
std::shared_ptr<int> ptr( new int );
thread1:
while ( ptr != 0 ) { /* ... */ }
thread2:
/* blablabla */
ptr.reset();
sauf que apparement shared_ptr est thread safe. Mais vous
pigez ma question : le test:
while ( ptr != 0 )
fait dans thread1 ne risque-t-il pas d'être faussé par la
modif de thread2 "en parallèle" ?
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.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Y'a plus simple dans mon cas : un QGuardedPtr est
automatiquement mis à NULL par Qt une fois l'objet pointé
déréférencé.
Et ce n'est pas évident pour le faire marcher
correctement -- en gros, je dirais que l'utilisation du
pointeur en tant que pointeur ne doit avoir lieu que
dans le thread d'affichage, qui doit aussi être le seul
thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui
gère la destruction ; il peuvent donc bien s'arranger
pour que la destruction n'a pas lieu à un moment
importun. Ici, c'est Qt qui le gère, et il faut être
certain que Qt ne le fait pas quand on a déjà extrait le
pointeur brut, mais qu'on n'en a pas encore servi. AMHA,
la solution qui s'impose, c'est que le seul thread qui
se sert du pointeur brut, c'est le même thread que celui
qui pourrait éventuellement faire un delete sur l'objet.
Y'a pas de delete. Dans mon cas ça s'utilise comme ça:
void MainWnd::OnDoSomething()
{
DoSomethingDialog dialog( this );
dialog.exec();
}
et le exec() lance un thread et affiche une barre de
progression qui suit l'évolution du traitement.
Si le programmeur n'a pas géré la fermeture de
DoSomethingDialog de manière à interrompre le thread / ou
attendre qu'il se termine, exec() rend la main, on sort de
OnDoSomething() et le dialog est détruit, mais le thread lui
continue d'envoyer un feedback... sur &dialog, et...2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Je suis obligé de passer par un QObject* pour envoyer un
évènement, car c'est un mécanisme interne à Qt. Le QObject
indiqué reçoit alors l'évènement via une fonction virtuelle
dédiée.Dans le deuxième cas, il faut bien que le thread de calcul
ait accès à l'objet d'affichage.
Ca c'est gênant, ça me fait une dépendance croisée
FenetreAffichage <-> ThreadCalcul.
Dans ce cas-là, il faut impérativement 1) qu'il soit
observeur, pour que le pointeur soit mis à null lors d'un
delete, et 2) qu'il detient un lock aussi longtemps qu'il a
une copie du pointeur, et que ce même lock protège la
deletion (c-à-d qu'autant qu'il a le lock, on ne peut pas
deleter).
Je ne peux empêcher le delete/destruction, ce serait fâcheux.
Dans ce cas ce n'est plus le thread principal qui controle le
thread de calcul, mais le 2° qui peut bloquer le 1°, et alors
la message pump l'est aussi, et donc toute l'appli aussi, y
compris l'envoie d'évènements, plus rien ne fonctionne... Je
préfère l'autre sens : le thread d'affichage tue le process
avant de fermer le dialog, ou plus proprement lui dit de
s'arrêter (ce qui est logique même si je n'avais pas ce
probleme).Pour 2), j'aime plutôt l'idée d'un pointeur intelligent dont
le constructeur aquiert le lock, et le dernier destructeur
le libère. On peut se servir de boost::smart_ptr ici, à
condition d'acquérir le lock avant de créer le pointeur, et
que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère
le lock (et qui ne delete rien). Évidemment, il faut que le
code qui effectue le delete de l'objet prend le même lock.
(Je suis assez scéptique sur la prise d'un lock dans un
destructeur. Mais à la rigueur, comme première action dans
le destructeur de la classe la plus dérivée. Ce qu'il faut
tenir en compte, c'est qu'entre le moment que la décision a
été prise de detruire l'objet et le moment que le thread
d'affichage prend le lock, le thread de calcul peut
interrompre, obtenir un pointeur à l'objet d'affichage, et
s'en servir. Or, si on est déjà dans le destructeur...)
Oui je comprend. Mais je vais faire plus simple. Au lieu de
passer un QObject* simple à mon thread de calcul (pour envoyer
le feedback), je vais lui donner une structure "concurrent
access safe" qui permet de savoir si l'arrêt du traitement a
été demandé. Si c'est le cas il n'envoie plus d'évènements et
se termine
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments. Pour mettre à jour un barre
d'avancement, par exemple, on envoie un évenement au thread
gestionnaire des événements, qui s'en occupe. L'alternatif,
c'est (en supposant un MVC) que le thread de calcul fasse la
mise à jour du modèle, puis envoie un événemment au thread
d'affichage pour l'informer qu'il y a eu un changement.
C'est exactement cela. Le probleme est dans l'envoie de
l'évènement. Pour cela, je dois spécifier la fenêtre
destinatrice. Mon probleme : si par la suite cette fenêtre est
détruite, l'envoie d'évènement fait tout crasher (pointeur
null reference).
Qt aurait pu être mieux fouttu sur ce point mais c'est pas le
cas. Alors j'ai pris mes précautions, ça ne se produira plus,
mais bon j'aime pas avoir ce genre de sournoiserie qui
traine...
Ce qui m'est imposé par Qt, c'est d'utiliser un QObject* comme
destinataire de l'évènement. Le probleme c'est que si ce
QObject* est détruit par la suite, boum badaboum.
Solution : j'utilise un QGuardedPtr<QObject>, qui a une
particularité : être automatiquement mis à NULL si l'objet
pointé est détruit. Ca marche très bien.
Ma question : le fait que ce QGuardedPtr soit mis à NULL par
un autre thread, je me demande si une optimisation du compilo
ne risque pas de lui faire louper l'info.
C'est un peu comme si on avait ça:
std::shared_ptr<int> ptr( new int );
thread1:
while ( ptr != 0 ) { /* ... */ }
thread2:
/* blablabla */
ptr.reset();
sauf que apparement shared_ptr est thread safe. Mais vous
pigez ma question : le test:
while ( ptr != 0 )
fait dans thread1 ne risque-t-il pas d'être faussé par la
modif de thread2 "en parallèle" ?
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.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Y'a plus simple dans mon cas : un QGuardedPtr est
automatiquement mis à NULL par Qt une fois l'objet pointé
déréférencé.
Et ce n'est pas évident pour le faire marcher
correctement -- en gros, je dirais que l'utilisation du
pointeur en tant que pointeur ne doit avoir lieu que
dans le thread d'affichage, qui doit aussi être le seul
thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui
gère la destruction ; il peuvent donc bien s'arranger
pour que la destruction n'a pas lieu à un moment
importun. Ici, c'est Qt qui le gère, et il faut être
certain que Qt ne le fait pas quand on a déjà extrait le
pointeur brut, mais qu'on n'en a pas encore servi. AMHA,
la solution qui s'impose, c'est que le seul thread qui
se sert du pointeur brut, c'est le même thread que celui
qui pourrait éventuellement faire un delete sur l'objet.
Y'a pas de delete. Dans mon cas ça s'utilise comme ça:
void MainWnd::OnDoSomething()
{
DoSomethingDialog dialog( this );
dialog.exec();
}
et le exec() lance un thread et affiche une barre de
progression qui suit l'évolution du traitement.
Si le programmeur n'a pas géré la fermeture de
DoSomethingDialog de manière à interrompre le thread / ou
attendre qu'il se termine, exec() rend la main, on sort de
OnDoSomething() et le dialog est détruit, mais le thread lui
continue d'envoyer un feedback... sur &dialog, et...
2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Je suis obligé de passer par un QObject* pour envoyer un
évènement, car c'est un mécanisme interne à Qt. Le QObject
indiqué reçoit alors l'évènement via une fonction virtuelle
dédiée.
Dans le deuxième cas, il faut bien que le thread de calcul
ait accès à l'objet d'affichage.
Ca c'est gênant, ça me fait une dépendance croisée
FenetreAffichage <-> ThreadCalcul.
Dans ce cas-là, il faut impérativement 1) qu'il soit
observeur, pour que le pointeur soit mis à null lors d'un
delete, et 2) qu'il detient un lock aussi longtemps qu'il a
une copie du pointeur, et que ce même lock protège la
deletion (c-à-d qu'autant qu'il a le lock, on ne peut pas
deleter).
Je ne peux empêcher le delete/destruction, ce serait fâcheux.
Dans ce cas ce n'est plus le thread principal qui controle le
thread de calcul, mais le 2° qui peut bloquer le 1°, et alors
la message pump l'est aussi, et donc toute l'appli aussi, y
compris l'envoie d'évènements, plus rien ne fonctionne... Je
préfère l'autre sens : le thread d'affichage tue le process
avant de fermer le dialog, ou plus proprement lui dit de
s'arrêter (ce qui est logique même si je n'avais pas ce
probleme).
Pour 2), j'aime plutôt l'idée d'un pointeur intelligent dont
le constructeur aquiert le lock, et le dernier destructeur
le libère. On peut se servir de boost::smart_ptr ici, à
condition d'acquérir le lock avant de créer le pointeur, et
que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère
le lock (et qui ne delete rien). Évidemment, il faut que le
code qui effectue le delete de l'objet prend le même lock.
(Je suis assez scéptique sur la prise d'un lock dans un
destructeur. Mais à la rigueur, comme première action dans
le destructeur de la classe la plus dérivée. Ce qu'il faut
tenir en compte, c'est qu'entre le moment que la décision a
été prise de detruire l'objet et le moment que le thread
d'affichage prend le lock, le thread de calcul peut
interrompre, obtenir un pointeur à l'objet d'affichage, et
s'en servir. Or, si on est déjà dans le destructeur...)
Oui je comprend. Mais je vais faire plus simple. Au lieu de
passer un QObject* simple à mon thread de calcul (pour envoyer
le feedback), je vais lui donner une structure "concurrent
access safe" qui permet de savoir si l'arrêt du traitement a
été demandé. Si c'est le cas il n'envoie plus d'évènements et
se termine
Dans son cas, la solution serait sans doute celle imposée
par Qt, que je connais pas. Dans les systèmes de fenêtrage
que je connais, toutes les mises à jours des graphiques se
font dans un thread particulier, qui est aussi celui que
gère les évenemments. Pour mettre à jour un barre
d'avancement, par exemple, on envoie un évenement au thread
gestionnaire des événements, qui s'en occupe. L'alternatif,
c'est (en supposant un MVC) que le thread de calcul fasse la
mise à jour du modèle, puis envoie un événemment au thread
d'affichage pour l'informer qu'il y a eu un changement.
C'est exactement cela. Le probleme est dans l'envoie de
l'évènement. Pour cela, je dois spécifier la fenêtre
destinatrice. Mon probleme : si par la suite cette fenêtre est
détruite, l'envoie d'évènement fait tout crasher (pointeur
null reference).
Qt aurait pu être mieux fouttu sur ce point mais c'est pas le
cas. Alors j'ai pris mes précautions, ça ne se produira plus,
mais bon j'aime pas avoir ce genre de sournoiserie qui
traine...
Ce qui m'est imposé par Qt, c'est d'utiliser un QObject* comme
destinataire de l'évènement. Le probleme c'est que si ce
QObject* est détruit par la suite, boum badaboum.
Solution : j'utilise un QGuardedPtr<QObject>, qui a une
particularité : être automatiquement mis à NULL si l'objet
pointé est détruit. Ca marche très bien.
Ma question : le fait que ce QGuardedPtr soit mis à NULL par
un autre thread, je me demande si une optimisation du compilo
ne risque pas de lui faire louper l'info.
C'est un peu comme si on avait ça:
std::shared_ptr<int> ptr( new int );
thread1:
while ( ptr != 0 ) { /* ... */ }
thread2:
/* blablabla */
ptr.reset();
sauf que apparement shared_ptr est thread safe. Mais vous
pigez ma question : le test:
while ( ptr != 0 )
fait dans thread1 ne risque-t-il pas d'être faussé par la
modif de thread2 "en parallèle" ?
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.
Dans le premier cas, il y a deux solutions :
1. On se sert de l'équivalent d'un boost::weak_ptr dans le
thread de calcul et dans le message. La conversion en
shared_ptr n'a lieu que dans le thread d'affichage, avec les
tests qu'il faut.
Je dis l'équivalent, parce qu'évidemment, les objets gérés
par Qt ne sont pas gérés par boost::shared_ptr. En gros, si
Qt n'offre pas de support direct pour ce genre de chose, il
faudrait dériver de la classe d'affichage, et s'arranger que
le pointeur, et toutes les copies du pointeur, soient
connues de cette classe, pour qu'elle puisse les mettre à
nul dans les pointeurs.
Y'a plus simple dans mon cas : un QGuardedPtr est
automatiquement mis à NULL par Qt une fois l'objet pointé
déréférencé.
Et ce n'est pas évident pour le faire marcher
correctement -- en gros, je dirais que l'utilisation du
pointeur en tant que pointeur ne doit avoir lieu que
dans le thread d'affichage, qui doit aussi être le seul
thread qui pourrait détruire l'objet d'affichage.
Chez Boost, c'est l'ensemble shared_ptr/weak_ptr qui
gère la destruction ; il peuvent donc bien s'arranger
pour que la destruction n'a pas lieu à un moment
importun. Ici, c'est Qt qui le gère, et il faut être
certain que Qt ne le fait pas quand on a déjà extrait le
pointeur brut, mais qu'on n'en a pas encore servi. AMHA,
la solution qui s'impose, c'est que le seul thread qui
se sert du pointeur brut, c'est le même thread que celui
qui pourrait éventuellement faire un delete sur l'objet.
Y'a pas de delete. Dans mon cas ça s'utilise comme ça:
void MainWnd::OnDoSomething()
{
DoSomethingDialog dialog( this );
dialog.exec();
}
et le exec() lance un thread et affiche une barre de
progression qui suit l'évolution du traitement.
Si le programmeur n'a pas géré la fermeture de
DoSomethingDialog de manière à interrompre le thread / ou
attendre qu'il se termine, exec() rend la main, on sort de
OnDoSomething() et le dialog est détruit, mais le thread lui
continue d'envoyer un feedback... sur &dialog, et...2. À la place d'un pointeur, le thread de calcul a un
identificateur quelconque (entier, string...). Il envoie le
message avec l'identificateur, et c'est le thread
d'affichage qui le cherche dans un map -- s'il n'est pas là,
le thread d'affichage laisse tomber la mise à jour.
Je suis obligé de passer par un QObject* pour envoyer un
évènement, car c'est un mécanisme interne à Qt. Le QObject
indiqué reçoit alors l'évènement via une fonction virtuelle
dédiée.Dans le deuxième cas, il faut bien que le thread de calcul
ait accès à l'objet d'affichage.
Ca c'est gênant, ça me fait une dépendance croisée
FenetreAffichage <-> ThreadCalcul.
Dans ce cas-là, il faut impérativement 1) qu'il soit
observeur, pour que le pointeur soit mis à null lors d'un
delete, et 2) qu'il detient un lock aussi longtemps qu'il a
une copie du pointeur, et que ce même lock protège la
deletion (c-à-d qu'autant qu'il a le lock, on ne peut pas
deleter).
Je ne peux empêcher le delete/destruction, ce serait fâcheux.
Dans ce cas ce n'est plus le thread principal qui controle le
thread de calcul, mais le 2° qui peut bloquer le 1°, et alors
la message pump l'est aussi, et donc toute l'appli aussi, y
compris l'envoie d'évènements, plus rien ne fonctionne... Je
préfère l'autre sens : le thread d'affichage tue le process
avant de fermer le dialog, ou plus proprement lui dit de
s'arrêter (ce qui est logique même si je n'avais pas ce
probleme).Pour 2), j'aime plutôt l'idée d'un pointeur intelligent dont
le constructeur aquiert le lock, et le dernier destructeur
le libère. On peut se servir de boost::smart_ptr ici, à
condition d'acquérir le lock avant de créer le pointeur, et
que lors de la création initiale, on utilise le constructeur
à deux paramètres, pour lui passer un « deleter » qui libère
le lock (et qui ne delete rien). Évidemment, il faut que le
code qui effectue le delete de l'objet prend le même lock.
(Je suis assez scéptique sur la prise d'un lock dans un
destructeur. Mais à la rigueur, comme première action dans
le destructeur de la classe la plus dérivée. Ce qu'il faut
tenir en compte, c'est qu'entre le moment que la décision a
été prise de detruire l'objet et le moment que le thread
d'affichage prend le lock, le thread de calcul peut
interrompre, obtenir un pointeur à l'objet d'affichage, et
s'en servir. Or, si on est déjà dans le destructeur...)
Oui je comprend. Mais je vais faire plus simple. Au lieu de
passer un QObject* simple à mon thread de calcul (pour envoyer
le feedback), je vais lui donner une structure "concurrent
access safe" qui permet de savoir si l'arrêt du traitement a
été demandé. Si c'est le cas il n'envoie plus d'évènements et
se termine