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

10 réponses

1 2 3
Avatar
Jean-Marc Bourguet
"Aurélien REGAT-BARREL" writes:

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


Volatile n'est vraissemblablement pas la solution d'un probleme de
multithread sauf si c'est documente explicitement par ton compilateur
et que tu n'envisages pas qu'un autre soit possible.

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

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


C'est quoi l'erreur?

<boule de cristal>
Si tu fais la meme chose avec const, qu'est que tu as comme erreur?
</boule de cristal>

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Aurélien REGAT-BARREL
Volatile n'est vraissemblablement pas la solution d'un probleme de
multithread sauf si c'est documente explicitement par ton compilateur
et que tu n'envisages pas qu'un autre soit possible.


Hum, je vois pas trop ce que je peux faire, car c'est dans le cadre d'une
lib spéciale : Qt. Les objets Qt dérivent de QObject et c'est Qt qui gère
leur durée de vie (pas via des smart ptr, mais via une relation
parent-enfant : détruire le parent détruit les enfants, plus d'autres
astuces).
Je lance un thread depuis une fenêtre, la fenêtre affiche l'avancement du
thread. Si jamais la fenêtre est fermée avant que le thread ne se termine,
Qt va détruire l'objet fenêtre, et le pointeur donné au thread devient
invalide, et ça plante quand le thread envoie des évènements à la fenêtre
pour signaler la progression de son traitement.
Idéalement il "suffit" de veiller à ne pas détruire la fenêtre avant le
thread. Mais tant qu'à faire, j'aimerais blinder le truc.
La solution que j'ai adopté c'est d'utiliser un pointeur particulier (genre
auto_ptr en fait mais sauce Qt) : sauf que là quand la fenêtre est détruite
le pointeur est mis à NULL automatiquement par Qt. C'est cool ça marche
bien. Reste que ce pointeur est mis à NULL par un autre thread que celui qui
l'utilise, d'où ma volonté de mettre volatile, pour être parfait. Sauf
que...

C'est quoi l'erreur?


Alors pour l'exemple donné l'erreur avec VC++ 7.1 c'est:

error C2451: expression conditionnelle de type
'volatile boost::shared_ptr<T>' non conforme
with
[
T=int
]

et j'ai droit aussi à un warning

warning C4127: l'expression conditionnelle est une constante

const ne change rien, const à la place de volatile ça marche.

oublions boost::shared_ptr et nouvel essai avec auto_ptr:

volatile auto_ptr<int> v( new int );
if ( *v == 10 )
{
}

VC++ 7.1 me dit:

error C2678: '*' binaire : aucun opérateur trouvé qui
accepte un opérande de partie gauche de type
'volatile std::auto_ptr<_Ty>' (ou il n'existe pas
de conversion acceptable)
with
[
_Ty=int
]

devcpp me dit:

passing `volatile std::auto_ptr<int>' as `this' argument of
`_Tp& std::auto_ptr<_Tp>::operator*() const [with _Tp = int]'
discards

--
Aurélien REGAT-BARREL

Avatar
Jean-Marc Bourguet
"Aurélien REGAT-BARREL" writes:

C'est cool ça marche
bien. Reste que ce pointeur est mis à NULL par un autre thread que celui qui
l'utilise, d'où ma volonté de mettre volatile, pour être parfait.


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.

que...

C'est quoi l'erreur?


Alors pour l'exemple donné l'erreur avec VC++ 7.1 c'est:

error C2451: expression conditionnelle de type
'volatile boost::shared_ptr<T>' non conforme


Pas trop clair effectivement. Je ne sais pas si ma supposition est
bonne et j'ai pas le temps d'aller voir boost.

volatile fonctionne comme const, pour appeler des membres sur un objet
const, il faut que ces membres soient const. Pour appeler des membres
sur un objet volatile, il faut que ces membres soient volatiles.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Fabien LE LEZ
On Thu, 3 Feb 2005 17:44:08 +0100, "Aurélien REGAT-BARREL"
:

Je lance un thread depuis une fenêtre, la fenêtre affiche l'avancement du
thread. Si jamais la fenêtre est fermée avant que le thread ne se termine,
Qt va détruire l'objet fenêtre, et le pointeur donné au thread devient
invalide,


Tu as donc une classe Fenetre et une classe ThreadCalcul. Ce bout de
code devrait fonctionner :

Fenetre::~Fenetre()
{
pointeur_thread_calcul-> NotificationDestructionFenetre();
}

class ThreadCalcul
{
public:
void NotificationDestructionFenetre()
{
HandleCriticalSection::Lock (mutex);
la_fenetre_existe= false;
}

private:
bool la_fenetre_existe;
HandleCriticalSection mutex;

bool LaFenetreExiste()
{
HandleCriticalSection::Lock (mutex);
return la_fenetre_existe;
}

void MiseAJourFenetre()
{
if (LaFenetreExiste())
fenetre-> MiseAJourAffichage();
}
};

http://perso.edulang.com/fclc++/critical_section.h.zip


--
;-)

Avatar
Fabien LE LEZ
On 03 Feb 2005 16:28:06 +0100, Jean-Marc Bourguet :

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.


--
;-)

Avatar
Fabien LE LEZ
On Thu, 03 Feb 2005 18:09:45 +0100, Fabien LE LEZ
:

bool la_fenetre_existe;
HandleCriticalSection mutex;


En fait, il vaut mieux enfermer tout ça dans une classe :

template <class T> class VariableAvecMutex
{
public:
void Set (T const& nouvelle_valeur)
{
HandleCriticalSection::Lock (mutex);
valeur= nouvelle_valeur;
}

T Get()
{
HandleCriticalSection::Lock (mutex);
return valeur;
}

private:
HandleCriticalSection mutex;
T valeur;
};

D'où :

class ThreadCalcul
{
public:
void NotificationDestructionFenetre()
{
la_fenetre_existe.Set(false);
}

private:
VariableAvecMutex<bool> la_fenetre_existe;

void MiseAJourFenetre()
{
if (la_fenetre_existe.Get())
fenetre-> MiseAJourAffichage();
}
};

Ne pas oublier de mettre un "la_fenetre_existe.Set(true);" quelque
part...


--
;-)

Avatar
kanze
Fabien LE LEZ wrote:
On 03 Feb 2005 16:28:06 +0100, Jean-Marc Bourguet :

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.


Ça dépend de la définition de « thread ». Les threads actuels ne
sont pas si différents des processus que j'ai connu à l'époque
d'avant memory mapping -- où tous les processus avaient accès à
toutes la mémoire.

En fait, volatile a été conçu surtout pour les entrées/sorties
mappées mémoire. Sur des systèmes simples et primitifs, ils
permettaient des synchronisations entre des interruptions et le
processus principal ou entre deux processus aussi. Mais un PC
moderne, c'est loin d'être un système simple et primitif. (Et en
passant, ce n'est pas réelement le système d'exploitation qui
fait la différence, mais la gestion de la mémoire. Sans cache,
et où une instruction de load ou de store bloque le processeur
jusuqu'à ce qu'elle a fini, volatile a un sens. C-à-d chez
Intel, jusqu'au 80386 à peu près.)

--
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
Jean-Marc Bourguet
Fabien LE LEZ writes:

On 03 Feb 2005 16:28:06 +0100, Jean-Marc Bourguet :

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.


Ca peut toujours servir sous Windows ou Unix. D'abord dans les cas
prevus (longjmp), ensuite dans les modules de gestions de
peripheriques ou la "memoire" est quelque chose de bien particulier.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
kanze
Fabien LE LEZ wrote:
On Thu, 3 Feb 2005 17:44:08 +0100, "Aurélien REGAT-BARREL"
:

Je lance un thread depuis une fenêtre, la fenêtre affiche
l'avancement du thread. Si jamais la fenêtre est fermée avant
que le thread ne se termine, Qt va détruire l'objet fenêtre,
et le pointeur donné au thread devient invalide,


Tu as donc une classe Fenetre et une classe ThreadCalcul.


Laissons tomber les classes jusqu'à ce qu'on a précisé la
dynamique voulue.

Ce bout de code devrait fonctionner :

Fenetre::~Fenetre()
{
pointeur_thread_calcul-> NotificationDestructionFenetre();
}

class ThreadCalcul
{
public:
void NotificationDestructionFenetre()
{
HandleCriticalSection::Lock (mutex);
la_fenetre_existe= false;
}

private:
bool la_fenetre_existe;
HandleCriticalSection mutex;

bool LaFenetreExiste()
{
HandleCriticalSection::Lock (mutex);
return la_fenetre_existe;
}

void MiseAJourFenetre()
{
if (LaFenetreExiste())


Changement de thread, invalidation du pointeur, et...

fenetre-> MiseAJourAffichage();


Bang !

}
};


Ce n'est pas comme ça qu'on gère les threads. La granularité est
trop petite.

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.

Ici aussi, il importe que le delete de l'objet d'affichage
ne peut avoir lieu que dans le thread d'affichage.

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...)

--
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
Olivier Azeau
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.

1 2 3