IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Threads & Processus C++ Discussion :

Suis-je thread safe ?


Sujet :

Threads & Processus C++

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    38
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 38
    Points : 19
    Points
    19
    Par défaut Suis-je thread safe ?
    Bonjour,
    J'ai une Dll qui load une 2ème dll.Dans la 1ère DLL, mon code ressemble à ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void * fonction1;
     
    void initDLL2(char * nomDLL2){
         if(loadDLL2==OK){
              fonction1=getProcAdress("fonction1");
         }
    }
     
    void superfonction(....){
     
        if(!isLoadedDLL2){
             initDLL2("nomDLL2");
        }
        ...
        fonction1(...);
        ...
    }
    Seule superfonction est accessible en dehors de la dll, mais elle est appelée sans arrêt.

    A votre avis, suis-je thread safe (c'est impératif !!).
    D'un côté non car ma fonction est une variable globale, mais en même temps qu'est -ce que je risque ? Ma fonction1 ne peut pas être modifiée, donc ça pourrait être OK. Non ?
    Et au moment de l'initDLL2, que peut-il se passer ?

    A votre avis ?
    Merci

  2. #2
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Tu me sembles être dans cette situation : http://www.justsoftwaresolutions.co....d-locking.html

    Personnellement, je fuis comme la peste les init paresseuses pour les rendre explicites et obligatoires dans le thread principal au moment du chargement du programme.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  3. #3
    Membre à l'essai
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    38
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 38
    Points : 19
    Points
    19
    Par défaut
    Merci de votre réponse....bien bien technique pour mon niveau.
    Effectivement, l'article a bien l'air de "parler de mon cas", mais pour résoudre le problème, il utilise une librairie que je n'ai pas.

    Existe -il un autre moyen ?
    Je ne peux pas loader DLL2 dans un DllMain car je ne connais pas le nom de la DLL à loader au moment du chargement de la 1ère. C'est le programme appelant qui doit me le donner. De plus, la dll est utilisée/wrappée dans matlab et labview (voir d'autres environnements) et je ne suis pas sûre qu'ils tiennent compte d'un dllMain (labview oui je crois mais matlab...).

    La solution que vous me préconiser serait (si j'ai bien compris) de rendre obligatoire l'appel direct à initDLL2 à partir du programme appelant. Je vais regarder si ça pourrait marcher en matlab.

    Mais sinon, dans le pire de cas, qu'est ce que je risque ?

  4. #4
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Sous Windows, je me suis fait un mécanisme à base de Interlocked et de booléens pour faire en toute sécurité des initialisations paresseuses:
    Code C/C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /*OnceAProcess.h*/
    #pragma once
     
    #ifndef EXTERN_C
    	#ifdef __cplusplus
    		#define EXTERN_C extern "C"
    	#else
    		#define EXTERN_C extern
    	#endif
    #endif
     
    typedef LRESULT (*ONCEAPROCESSCALLBACK)(LPARAM userParam);
     
    EXTERN_C LRESULT DoOnceAProcess(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     ONCEAPROCESSCALLBACK proc,      /*[in] Callback function, called once in the process. Must not be NULL.*/
     LPARAM userData,                /*[in/bypass] User parameter passed to the callback function.*/
     LRESULT defReturnVal            /*[in/bypass] Value to return if the function was NOT called.*/
     ); /*Returns the callback's return value, or defReturnVal.*/
    EXTERN_C HRESULT AllocateTlsSlotOnce(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     DWORD * pdwTlsIndex             /*[out] Return the TLS slot here, leave untouched if already done.*/
     ); /*Returns S_FALSE if was already allocated.*/
    EXTERN_C HRESULT InitializeCriticalSectionOnce(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LPCRITICAL_SECTION pCrit        /*[out] Ptr to the critical section to initialize.*/
     ); /*Returns S_FALSE if was already initialized.*/
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include "OnceAProcess.h"
     
    #ifdef SANS_CRT
    	#include "TestSansCRT.h"
    	#define assert MY_ASSERT
    #else
    	#include <assert.h>
    #endif
     
    /*
    Do something once in a process
    ------------------------------ */
    EXTERN_C LRESULT DoOnceAProcess(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     ONCEAPROCESSCALLBACK proc,      /*[in] Callback function, called once in the process. Must not be NULL.*/
     LPARAM userData,                /*[in/bypass] User parameter passed to the callback function.*/
     LRESULT defReturnVal            /*[in/bypass] Value to return if the function was NOT called.*/
     ) /*Returns the callback's return value, or defReturnVal.*/
    {
    	static const LONG lTrue = TRUE;
    	static const LONG lFalse = FALSE;
     
    	LRESULT ret = defReturnVal;
     
    	if( ! *pbDone )
    	{
    		/* If not already started doing, do it.
    		   Else, wait actively until done */
     
    		/* InterlockedCompareExchange() :
    		   If *pbStartedDoing == lFalse, set it to lTrue; return the previous value of *pbStartedDoing. */
    		LONG alreadyStartedDoing = InterlockedCompareExchange(pbStartedDoing, lTrue, lFalse);
    		if(alreadyStartedDoing == lFalse)
    		{
    			ret = proc(userData);
     
    			*pbDone = lTrue;
    		}
    		else
    		{
    			while( ! (*pbDone) ) {}
    		}
    	}
    	/* Now, *pbDone can only be true:
    	   Either it was already done, or it has just been done. */
    	assert(*pbDone);
     
    	return ret;
    }
     
     
    /* TLS slot initialization callback
       -------------------------------- */
    static LRESULT AllocTlsIndexToPtr(LPARAM parPdwTlsIndex)
    {
    	HRESULT hrRet = S_OK;
    	DWORD * pdwTlsIndex = (DWORD*)parPdwTlsIndex;
    	DWORD tlsIndex;
    	assert(pdwTlsIndex!=NULL);
     
    	tlsIndex = TlsAlloc();
    	if(tlsIndex==TLS_OUT_OF_INDEXES)
    	{
    		/* Error: Get an error value from the Win32 error code. */
    		HRESULT hrError = HRESULT_FROM_WIN32(GetLastError());
    		if(SUCCEEDED(hrError))
    			hrError = E_UNEXPECTED;
    		assert(FAILED(hrError));
    		/* return it. */
    		hrRet = hrError;
    	}
    	*pdwTlsIndex = tlsIndex;
    	return (LRESULT)hrRet;
    }
     
    /* Allocate a Thread-Local Storage slot once in a process
       ------------------------------------------------------ */
    EXTERN_C HRESULT AllocateTlsSlotOnce(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     DWORD * pdwTlsIndex             /*[out] Return the TLS slot here, leave untouched if already done.*/
     ) /*Returns S_FALSE if was already allocated.*/
    {
    	assert(pbStartedDoing != NULL);
    	assert(pbDone != NULL);
    	assert(pdwTlsIndex != NULL);
     
    	return (HRESULT)DoOnceAProcess(pbStartedDoing, pbDone, AllocTlsIndexToPtr, (LPARAM)pdwTlsIndex, (LRESULT)S_FALSE);
    }
     
     
    /* Critical section initialization callback
       ---------------------------------------- */
    static LRESULT InitializeCriticalSectionToPtr(LPARAM parPCrit)
    {
    	HRESULT hrRet = S_OK;
    	LPCRITICAL_SECTION pCrit = (LPCRITICAL_SECTION)parPCrit;
    	assert(pCrit!=NULL);
     
    	#ifdef SANS_CRT
    	//No CRT means no exception handling : Just pray.
    	InitializeCriticalSection(pCrit);
    	#else
    	__try
    	{
    		InitializeCriticalSection(pCrit);
    	}
    	__except(EXCEPTION_EXECUTE_HANDLER)
    	{
    		if(GetExceptionCode()==STATUS_NO_MEMORY)
    			hrRet = E_OUTOFMEMORY;
    		else
    			hrRet = E_UNEXPECTED;
    	}
    	#endif
    	return (LRESULT)hrRet;
    }
     
    /* Initialize a global critical section once in a process
       ------------------------------------------------------ */
    EXTERN_C HRESULT InitializeCriticalSectionOnce(
     LONG volatile * pbStartedDoing, /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LONG volatile * pbDone,         /*[in/out] Ptr to a static variable. Must be initialized to (LONG)FALSE;*/
     LPCRITICAL_SECTION pCrit        /*[out] Ptr to the critical section to initialize.*/
     ) /*Returns S_FALSE if was already initialized.*/
    {
    	assert(pbStartedDoing != NULL);
    	assert(pbDone != NULL);
    	assert(pCrit != NULL);
     
    	return (HRESULT)DoOnceAProcess(pbStartedDoing, pbDone, InitializeCriticalSectionToPtr, (LPARAM)pCrit, (LRESULT)S_FALSE);
    }
    La première fonction est la fonction standard avec laquelle tu peux faire ce que tu veux.
    Les deux autres fonctions exportées, et les callbacks associées, sont deux exemples permettant respectivement d'allouer un emplacement de Thread-Local Storage (TLS) et d'initialiser une CRITICAL_SECTION.

    Fonctionnement: La fonction utilise DEUX flags pour son double-checked locking: Un pour le fait que ce soit terminé, un pour le fait que quelqu'un ait commencé.
    • Lors de l'appel, la fonction regarde si c'est terminé, et si oui, quitte.
    • Si ça n'est pas terminé, la fonction fait un TAS sur le flag "commencé".
    • Si le commencement est acquis, la fonction effectue l'action puis positionne le flag "terminé".
    • Si le commencement n'est pas acquis, c'est qu'un autre thread l'a: La fonction fait juste une boucle d'attente active sur le flag "terminé" (je pourrais mettre un Sleep(0) ici).



    À quoi tout cela t'avance:
    Avec cette fonction et les exemples, tu devrais assez facilement pouvoir écrire une fonction callback qui fait le chargement de la seconde DLL.

    PS: Sinon, as-tu envisagé d'importer statiquement DLL2 ? si DLL1 en a besoin en toutes circonstances, c'est encore le plus simple, si DLL2 est fournie avec une bibliothèque statique d'importation...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  5. #5
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Personnellement, je fuis comme la peste les init paresseuses pour les rendre explicites et obligatoires dans le thread principal au moment du chargement du programme.
    personnelement je fuis comme la peste les initialisations globales car elles n'ont pas d'ordre prédéfinis et marchent seulement quelquefois

  6. #6
    Membre à l'essai
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    38
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 38
    Points : 19
    Points
    19
    Par défaut
    Ok, merci beaucoup pour vos réponses, je vais étudier tout ça.

  7. #7
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    Citation Envoyé par screetch Voir le message
    personnelement je fuis comme la peste les initialisations globales car elles n'ont pas d'ordre prédéfinis et marchent seulement quelquefois
    quelquefois?

    Bon le problème arrive quand deux initialisations dépendent l'une de l'autre. Mais il y a des moyens de forcer l'ordre.

    Exemple: std::cout/cerr etc... qui sont initialisés au démarrage avant toutes les autres initialisations ( et je ne connais pas de cas où ça ne marche pas ).

    Sinon si il n'y a pas de raison valables, je préfère aussi les initialisations explicites aux initialisations paresseuses, dans le main par exemple avant la création des threads.

  8. #8
    screetch
    Invité(e)
    Par défaut
    ca arrive souvent, les initialisations qui dépendent l'une de l'autre, et en général ca marche très bien jusqu'a ce que le compilateur décide de les réordonner un beau jour sur la machine d'un quidam qui ne comprend pas pourquoi ca marche plus.

    Et c'est une raison valable de fuir les inits statiques.

  9. #9
    Membre éprouvé
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Points : 1 176
    Points
    1 176
    Par défaut
    Citation Envoyé par screetch Voir le message
    ca arrive souvent, les initialisations qui dépendent l'une de l'autre, et en général ca marche très bien jusqu'a ce que le compilateur décide de les réordonner un beau jour sur la machine d'un quidam qui ne comprend pas pourquoi ca marche plus.

    Et c'est une raison valable de fuir les inits statiques.
    si elles sont indépendantes, je ne vois pas pourquoi je me priverais.

  10. #10
    screetch
    Invité(e)
    Par défaut
    c'est pas seulement une dependance de variable a depend de b
    cest plutot que l'initialisation de a appelle une fonction dans un autre module qui avait besoin que b soit initialisé
    dans la mesure ou le compilateur ne prévient pas de tels cas, lorsqu'une variable a besoin d'une initialisation de code, il vaut mieux toujours la rendre explicite ou paresseuse.
    better safe than sorry.

Discussions similaires

  1. [RCP] Treeviewer non thread-safe ?
    Par Guildux dans le forum Eclipse Platform
    Réponses: 4
    Dernier message: 09/01/2007, 13h00
  2. fonction de stdio.h thread safe ??
    Par boolzor dans le forum POSIX
    Réponses: 3
    Dernier message: 30/04/2006, 20h03
  3. Code "Thread Safe" ?
    Par Neitsa dans le forum C++
    Réponses: 3
    Dernier message: 23/12/2005, 14h33
  4. [Language]Immutable & Thread-Safe
    Par Repti dans le forum Concurrence et multi-thread
    Réponses: 4
    Dernier message: 21/12/2005, 15h50
  5. [MFC] CMAP non thread safe ?
    Par fmarot dans le forum MFC
    Réponses: 5
    Dernier message: 04/10/2005, 13h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo