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

C++ Discussion :

Changement de données à la volée


Sujet :

C++

  1. #1
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut Changement de données à la volée
    Bonjour !

    J'ai un service qui expose une classe COM à des clients.
    La classe COM lit des données pour les fournir à ses clients.
    Les données sont en lecture seule et sont mises à jour de temps en temps par d'autres processus, en bloc.
    Je souhaite changer les données à la volée sans arrêter le service.

    Supposons que les données soient gérées par une classe DataLinker:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class DataLinker
    {
    public:
    	DataLinker(std::_tstring);
    	bool Open();
    	...
    };
    Voici la classe DataLocker qui maintient les données en disponibilité, tant qu'au
    moins un client les utilise ou tant qu'il n'y a pas de nouvelles données disponibles,
    grâce à un compteur de référence:
    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
    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
    class DataLocker
    {
    public:
    	DataLocker() = default;
     
    private:
    	DataLocker(DataLocker const &) = delete;
    	DataLocker & operator=(DataLocker const &) = delete;
     
    	DataLinker *f_ptr_DataLinker = nullptr;
    	std::_tstring f_datapath;
    	LONG f_refcnt = 0l;
     
    public:
    	DataLinker *GetDataLinker()
    		{ return f_ptr_DataLinker; }
     
    	std::_tstring const & GetDataPath() const
    		{ return f_datapath; }
     
    	bool IsEmpty() const
    		{ return f_ptr_DataLinker == nullptr; }
     
    	void AddRef()
    	{
    		InterlockedIncrement(&f_refcnt);
    	}
     
    	void Release()
    	{
    		if (InterlockedDecrement(&f_refcnt) == 0)
    			Unload();
    	}
     
    	bool Load(std::_tstring const & datapath)
    	{
    		ATLASSUME(f_ptr_DataLinker == nullptr && f_refcnt == 0l);
    		DataLinker *ptr_DataLinker;
    		try
    		{
    			ptr_DataLinker = new DataLinker(datapath);
    			if (ptr_DataLinker == nullptr || !ptr_DataLinker->Open())
    			{
    				delete ptr_DataLinker;
    				return false;
    			}
    			f_ptr_DataLinker = ptr_DataLinker;
    			f_refcnt = 1l;//Increment(&f_refcnt);
    			f_datapath = datapath;
    			return true;
    		}
    		catch (std::exception & e)
    		{
    #ifdef _DEBUG
    			OutputDebugStringA(e.what());
    #endif
    			delete ptr_DataLinker;
    		}
    		return false;
    	}
     
    private:
    	void Unload()
    	{
    		ATLASSUME(f_ptr_DataLinker != nullptr && f_refcnt == 0l);
    		delete f_ptr_DataLinker;
    		f_ptr_DataLinker = nullptr;
    		//f_refcnt = 0l;
    		f_datapath.clear();
    	}
    };
    Voici la classe singleton DataSwitcher qui gère la disponibilité et le changement de données (un pooler avec deux DataLocker pour passer de l'un à l'autre):
    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
    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
    class DataSwitcher
    {
    private:
    	DataLocker f_pls1;
    	DataLocker f_pls2;
    	DataLocker *f_curpls = nullptr;
     
    	DataSwitcher()
    		{ CheckParams(); }
     
    public:
    	void CheckParams();
     
    	static DataSwitcher & Get();
     
    	DataLocker *Data()
    		{ return f_curpls; }
    };
     
    void DataSwitcher::CheckParams()
    {
    	//check if a DataLocker is available
    	DataLocker *next_pls = nullptr;
    	DataLocker *curr_pls = nullptr;
    	if (f_pls1.IsEmpty())
    	{
    		next_pls = &f_pls1;
    		curr_pls = &f_pls2;
    	}
    	else if (f_pls2.IsEmpty())
    	{
    		next_pls = &f_pls2;
    		curr_pls = &f_pls1;
    	}
    	if (next_pls)
    	{
    		unsigned somevalue = read_somevalue_somewhere();
    		std::_tstring datapath(_T("D:\\DATA_VERSION_") + std::to_tstring(somevalue) + _T("\\"););
    		if (datapath != curr_pls->GetDataPath())
    		{
    			//load...
    			if (next_pls->Load(datapath))
    			{
    				//... and switch
    				f_curpls = next_pls;
    				//release current
    				if (!curr_pls->IsEmpty())
    					curr_pls->Release();
    			}
    		}
    	}
    }
     
    DataSwitcher & DataSwitcher::Get()
    {
    	static DataSwitcher ds;
    	return ds;
    }
    Et pour finir voici le code à ajouter dans la classe COM, ou en début et fin d'utilisation de n'importe quel client:
    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
    18
    	DataLocker *f_curpls = nullptr;
     
    	...
     
    	HRESULT FinalConstruct()
    	{
    		f_curpls = DataSingleton::Get().Data();
    		if (f_curpls == nullptr)
    			return E_FAIL;
    		f_curpls->AddRef();
    		return S_OK;
    	}
     
    	void FinalRelease()
    	{
    		if (f_curpls)
    			f_curpls->Release();
    	}
    La méthode DataSwitcher::CheckParams() est appélée une fois lors de la construction unique de DataSwitcher dans sa méthode static Get(), et puis de temps en temps dans un background thread qui appelle CheckParams().

    J'ai trois doutes:
    -le compteur de référence de DataLocker est-il safe, ne va-t-il jamais passer sous zéro, ou rester toujours strictement supérieur à zéro ?
    -Est-ce que l'échange de DataLocker dans CheckParams() est safe, ainsi que son acquisition et incrémentation de référence dans le FinalConstruct() des clients ?
    -En fin de programme, il reste un DataLocker disponible. Où faire le dernier Release() proprement ?

    Voilà, merci, j'espère que j'ai été assez clair

  2. #2
    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
    En gros, tu fais un double-buffering inter-thread, avec de multiples lecteurs et écrivains? C'est le genre de truc qui est loin d'être évident. Je me demande si tu n'aurais pas besoin de trois buffers pour être sûr...
    L'idée du comptage pour le lock de lecture est bonne, mais je pense qu'il faudrait le lier à un objet de synchronisation (comme un sémaphore à un seul élement. Pas un mutex, car le lock est posé par le premier et déverrouillé par le dernier, qui n'est pas forcément sur le même thread...)
    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.

  3. #3
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    On peut comparer mon besoin à du double buffering, en simple et sans prise de tête.

    Actuellement, les données sont rendues disponibles au démarrage du service.
    Si un changement de données est requis, on arrête et redémarre le service (qui accède alors aux nouvelles données).
    Cela a lieu une fois par jour et ne prend que quelques secondes au pire.

    Là, je souhaite simplement changer les données sans arrêter le service. Donc sans empêcher de nouveaux clients de se connecter et sans éjecter ceux en cours. Le triple buffering est inutile je pense, vu la faible fréquence de changement (même une fois par heure ça resterait peu fréquent).

    Je pense que le code proposé va dans la bonne direction, mais j'ai un doute sur la concurrence entre les ligne 45 à 48 dans CheckParams() et les lignes 7 à 10 dans FinalConstruct(). C'est-à-dire entre le changement de données et l'arrivée d'un nouveau client.

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 071
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 071
    Points : 12 116
    Points
    12 116
    Par défaut
    Pourquoi ne pas utiliser les fonctions de notification de COM : COM Events et COM Sink ???
    Les Clients utiliseront les vieilles données s'ils le veulent mais ils le sauront.
    Vous changez juste l'objet racine et vous notifiez le changement.
    Le client gèrera le "switch" comme ça lui arrange car aucun des pointeurs COM ne seront détruits avec lui dessus.

  5. #5
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Ce n'est pas d'application dans ce cas-ci.

    Je résume le projet dont le fonctionnement est simple.
    Des clients instancient l'objet COM, en appelent 2 ou 3 méthodes, puis disposent de l'objet.
    Un objet n'est jamais instancié de manière prolongée, il n'y a pas de persistence.

    Les méthodes COM accèdent à des données qui sont figées entre deux mises-à-jour.
    La mise-à-jour est effectuée une fois par jour.
    Le code de l'objet COM tourne dans un service.
    Ce service est redémarré chaque jour, pour tenir compte des nouvelles données journalières.
    Pendant le redémarrage le service n'est donc pas disponible.

    Le redémarrage ne dure que quelques secondes, mais voilà, je souhaite éviter d'éjecter les clients durant ce bref instant.
    Et donc pour cela, maintenir les clients en cours avec les données de la veille, tout en autorisant les nouveaux à accéder aux données du jour.

  6. #6
    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
    Si c'est juste ça pour des données infréquentes, alors une simple technique de "commit atomique" avec un InterlockedExchangePointer() devrait suffire. Puis tu pourras laisser le comptage de références normal (qui normalement utilise InterlockedIncrement()+InterlockedDecrement()) faire son boulot.
    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.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,
    Citation Envoyé par camboui Voir le message
    Ce n'est pas d'application dans ce cas-ci.
    <snip>Ce service est redémarré chaque jour, pour tenir compte des nouvelles données journalières.
    Pendant le redémarrage le service n'est donc pas disponible.

    Le redémarrage ne dure que quelques secondes, mais voilà, je souhaite éviter d'éjecter les clients durant ce bref instant.
    J'ai peut être loupé quelque chose, mais, pour moi, si un service est indisponible, les clients qui y sont connectés en sont forcément éjectés, non

    La première chose à faire, si tu souhaites éviter d'éjecter les clients, c'est de faire en sorte que le service ne soit plus redémarré, pour qu'il puisse rester dispo 7/24, et que les clients qui y sont connectés ne soient plus éjectés. Et, plus important encore, de modifier tes spécifications pour les homogénéiser, car, pour l'instant, tu as deux spécifications contradictoires

    Cela pourrait nécessiter d'indiquer "d'une manière ou d'une autre" la période de transition (askAgain, occasionnant un délais de X avant de relancer la requête) de manière à s'assurer qu'un client effectuant une requête juste "au mauvais moment" (celui de la mise à jour des données) soit tenu au courant du fait qu'il est arrivé juste au moment de la mise à jour

    Et, bien sur, rien ne t'empêche (l'un dans l'autre) de veiller à garder les anciennes données "le temps nécessaire" à t'assurer que tous les clients se soient mis à jour (même si on peut considérer le fait que ce temps soit limité à 24heures maximum dans le cas présent )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    Euh... oui, merci koala, tu as répété mes propos et ma question avec tes mots
    Donc, actuellement, en production, j'ai un service qui éjecte ses clients pendant quelques secondes une fois par jour.
    A l'avenir je souhaite ne plus éjecter les clients. Bien sûr cela doit être transparent pour eux, il est hors de question de les réécrire, l'interface avec le serveur reste identique.
    C'est en interne, dans le code du service, que le changement de données doit se faire.

    Mon premier post contient un bout de code.
    Pose-t-il un soucis de concurrence quelque part ?
    En particulier dans CheckParams() et FinalConstruct() comme dit dans mon 3ème post ?


    Merci @medinoc.
    Justement, je me demandais s'il n'existait pas un moyen de faire de manière atomique à la fois un InterlockedExchangePointer() et un InterlockedIncrement().

  9. #9
    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
    Citation Envoyé par camboui Voir le message
    Merci @medinoc.
    Justement, je me demandais s'il n'existait pas un moyen de faire de manière atomique à la fois un InterlockedExchangePointer() et un InterlockedIncrement().
    Pas à ma connaissance, mais échanger atomiquement deux pointeurs refcountés ne devrait pas nécessiter un changement du comptage de références (et encore moins quand un seul des deux est "visible", ce qui est une condition de base pour que l'échange puisse être atomique).
    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.

  10. #10
    Membre éprouvé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Points : 937
    Points
    937
    Par défaut
    J'ai trouvé une solution simple qui ne nécessite aucun lock, celle de retarder la suppression du précédant jeu de donnée au lieu de le supprimer le plus vite possible quand le nouveau a pris le relais. Il n'y a aucune urgence finalement à le supprimer tôt. Il suffit de légèrement modifier CheckParams() pour cela.

    ...

    D'une manière plus générale, je me demande si l'usage de std::shared_ptr<> n'est pas la solution. Il semble que la copie de std::shared_ptr<> soit sécurisée au travers des threads sans précaution particulière.
    Au besoin, il existe des fonctions std::atomic...<> dédiées aux std::shared_ptr<>, mais je ne connais pas.

Discussions similaires

  1. [D2007] Changement de langue à la volée
    Par sovitec dans le forum Delphi
    Réponses: 1
    Dernier message: 10/07/2007, 13h06
  2. Réponses: 3
    Dernier message: 25/06/2007, 15h25
  3. Problème de changement de données personnelles
    Par BnA dans le forum Langage
    Réponses: 9
    Dernier message: 15/05/2006, 14h02
  4. [MySQL] Introduire une variable dans requête SQL, insérer des données à la volée
    Par Ronan.f dans le forum PHP & Base de données
    Réponses: 18
    Dernier message: 29/04/2006, 22h10
  5. Changement Des données entre deux bases
    Par Chakib dans le forum Access
    Réponses: 4
    Dernier message: 28/01/2006, 10h59

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