2016-10-25 1 views
1

La configuration de ma demande est un objet const partagé avec plusieurs threads. La configuration est stockée dans un emplacement centralisé et n'importe quel thread peut l'atteindre. J'ai essayé de construire une implémentation lockfree qui me permettrait de charger une nouvelle configuration tout en permettant aux autres threads de lire la dernière configuration connue.Lockfree Rechargement et partage des objets const

Mon implémentation actuelle a une course entre la mise à jour du shared_ptr et sa lecture.

template<typename T> 
class ConfigurationHolder 
{ 
public: 
    typedef std::shared_ptr<T> SPtr; 
    typedef std::shared_ptr<const T> CSPtr; 

    ConfigurationHolder() : m_active(new T()) {} 

    CSPtr get() const { return m_active; } // RACE - read 

    template<typename Reloader> 
    bool reload(Reloader reloader) 
    { 
     SPtr tmp(new T()); 
     if (!tmp) 
      return false; 
     if (!reloader(tmp)) 
      return false; 
     m_active=tmp; // RACE - write 
     return true; 
    } 

private: 
    CSPtr m_active; 
}; 

Je peux ajouter un shared_mutex pour l'accès en lecture/écriture problématique au shared_ptr, mais je suis à la recherche d'une solution qui gardera la lockfree de mise en œuvre.

EDIT: Ma version de GCC ne prend pas en charge atomic_exchange sur shared_ptr

EDIT2: Exigences Précision: je les lecteurs multiples et peut avoir plusieurs transbordeurs (bien que ce soit moins fréquent). Les lecteurs doivent contenir un objet de configuration et qu'il ne changera pas pendant qu'ils le lisent. Les anciens objets de configuration doivent être libérés lorsque le dernier lecteur est fini avec eux.

+0

Pouvez-vous élaborer le cas d'utilisation? Si vous avez beaucoup de lecteurs, et qu'un thread décide de recharger, alors 1) Vous avez besoin d'un sémaphore, pas de mutex et 2) Quand les autres threads devraient-ils commencer à lire la nouvelle valeur? Ce n'est pas clairement défini jusqu'à quand ils devraient lire l'ancien. – kabanus

+0

@kabanus Edité avec des clarifications. De plus, je voudrais éviter d'ajouter des mutex/sémaphores, si possible. – Shloim

+0

Vous décrivez exactement un sémaphore en écriture. Les opérations de lecture sont libres, et l'écriture ne peut être faite que lorsque le sémaphore est clair. Tout ce que vous implémentez avec des compteurs serait juste cela. En supposant que l'assignation du pointeur n'est pas atomique, (cela pourrait être avec std :: atomic ), vous devrez créer un sémaphore r/w. Je peux vous en montrer une, sinon je n'ai pas de solution. – kabanus

Répondre

1

Vous devriez simplement mettre à jour votre compilateur pour obtenir des opérations de pointeur atomiques partagées.

A défaut, l'envelopper dans un shared_timed_mutex. Ensuite, testez combien cela vous coûte.

Ces deux travaux seront moins laborieux que l'écriture correcte de votre propre système de pointeur partagé sans verrou.

Si vous devez:


C'est un hack, mais il pourrait fonctionner. C'est un style de lecture-copie-mise à jour sur le pointeur lui-même.

Avoir un std::vector<std::unique_ptr<std::shared_ptr<T>>>. Avoir un std::atomic<std::shared_ptr<T> const*> pointeur "en cours", et un std::atomic<std::size_t> active_readers. Le vector stocke votre vie encore en vie Lorsque vous voulez changer, appuyez sur un nouveau sur le dos. Conservez une copie de ce shared_ptr.

Maintenant, remplacez le pointeur "actuel" par le nouveau. Occupé-attendre jusqu'à active_readers frappe zéro, ou jusqu'à ce que vous vous ennuyez.

Si active_readers hit zéro, filtrez votre vector pour shared_ptr s avec une utilisation-compte de 1. Retirez-les du vector. Quoi qu'il en soit, déposez maintenant le shared_ptr supplémentaire que vous avez dans l'état que vous avez créé. Et fini d'écrire.

Si vous avez besoin de plusieurs graveurs, verrouillez ce processus à l'aide d'un mutex distinct. Du côté du lecteur, incrémentez le active_readers. Maintenant, chargez atomiquement le pointeur "courant", faites une copie locale du pointé vers shared_ptr, puis décrémentez active_readers.

Cependant, je viens d'écrire ceci. Donc, il contient probablement des bugs. Prouver la conception simultanée correcte est dur.

De loin la meilleure façon de rendre ce fiable est de mettre à niveau votre compilateur et obtenir des opérations atomiques sur un shared_ptr.


Ceci est probablement trop complexe et je pense que nous pouvons le configurer de sorte que le T sont nettoyés lorsque le dernier lecteur va, mais je vise pour l'exactitude plutôt que l'efficacité sur le recyclage T.


Avec synchronisation minimial sur les lecteurs, vous pouvez utiliser une variable de condition pour signaler qu'un lecteur se fait avec une donnée T; mais cela implique un petit conflit avec le fil de l'écrivain.


Pratiquement, les algorithmes sans verrouillage sont souvent plus lents que serrure à base, car la surcharge d'un mutex est pas aussi élevé que vous avez peur.

Un shared_timed_mutex envelopper un shared_ptr, où l'écrivain est simplement en train d'écraser la variable, va être sacrément rapide. Les lecteurs existants vont garder leur vieux shared_ptr très bien.