J’ai quelques questions concernant le modèle singleton comme documenté ici: http://msdn.microsoft.com/en-us/library/ff650316.aspx
Le code suivant est un extrait de l’article:
using System; public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new object(); private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } }
Plus précisément, dans l’exemple ci-dessus, est-il nécessaire de comparer l’instance à deux fois, avant et après le verrou? Est-ce nécessaire? Pourquoi ne pas effectuer le verrou en premier et faire la comparaison?
Y a-t-il un problème en simplifiant les choses suivantes?
public static Singleton Instance { get { lock (syncRoot) { if (instance == null) instance = new Singleton(); } return instance; } }
Est-ce que la serrure est chère?
Effectuer le locking est extrêmement coûteux par rapport à l’ instance != null
simple de vérification du pointeur instance != null
.
Le motif que vous voyez ici s’appelle le locking à double vérification . Son but est d’éviter l’opération de locking coûteuse qui ne sera nécessaire qu’une seule fois (lors de la première utilisation du singleton). L’implémentation est telle qu’elle doit également garantir que lorsque le singleton est initialisé, il n’y aura aucun bogue résultant des conditions de concurrence de thread.
Pensez-y de cette manière: un contrôle null
(sans lock
) est garanti pour vous donner une réponse utilisable correcte uniquement lorsque cette réponse est “oui, l’object est déjà construit”. Mais si la réponse est “non encore construite” alors vous n’avez pas assez d’informations car ce que vous vouliez vraiment savoir, c’est que ce n’est “pas encore construit et qu’aucun autre fil n’a l’intention de le construire sous peu “. Donc, vous utilisez la vérification externe comme un test initial très rapide et vous lancez la procédure appropriée, sans bug mais “coûteuse” (verrouiller puis vérifier) seulement si la réponse est “non”.
L’implémentation ci-dessus est assez bonne pour la plupart des cas, mais à ce stade, c’est une bonne idée d’aller lire l’article de Jon Skeet sur les singletons dans C # qui évalue également d’autres alternatives.
La version paresseuse:
public sealed class Singleton { static readonly Lazy lazy = new Lazy (() => new Singleton()); private Singleton() { } public static Singleton Instance => lazy.Value; }
Nécessite .NET 4 et C # 6.0 (VS2015) ou plus récent.
Effectuer un verrou: Assez bon marché (encore plus cher qu’un test nul).
Effectuer un locking quand un autre thread l’a: Vous obtenez le coût de tout ce qu’ils doivent encore faire pendant le locking, ajouté à votre propre temps.
Effectuer un verrou quand un autre thread l’a, et des dizaines d’autres threads l’attendent aussi: Crippling.
Pour des raisons de performances, vous voulez toujours avoir des verrous qu’un autre thread souhaite, pour la plus courte période de temps possible.
Bien sûr, il est plus facile de raisonner sur des verrous «larges» que restreints. Il est donc utile de commencer par les élargir et de les optimiser, mais dans certains cas, l’expérience et la familiarité
(Soit dit en passant, si vous pouvez simplement utiliser private static volatile Singleton instance = new Singleton()
ou si vous ne pouvez peut-être pas utiliser les singletons mais utiliser une classe statique à la place, les deux sont meilleures en ce qui concerne ces problèmes).
La raison est la performance. Si instance != null
(ce qui sera toujours le cas sauf la toute première fois), il n’est pas nécessaire de faire un lock
coûteux: deux threads accédant simultanément au singleton initialisé seraient synchronisés de manière inutile.
Dans presque tous les cas (c’est-à-dire: tous les cas sauf les tous premiers), l’ instance
ne sera pas nulle. L’acquisition d’un verrou est plus coûteuse qu’une simple vérification, alors vérifier une fois la valeur de l’ instance
avant le locking est une optimisation agréable et gratuite.
Ce modèle est appelé locking à double vérification: http://en.wikipedia.org/wiki/Double-checked_locking
Jeffrey Richter recommande:
public sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if(instance != null) return instance; Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance; } } }
Vous pouvez créer avec empressement une instance Singleton adaptée aux threads, en fonction des besoins de votre application, c’est un code succinct, même si je préférerais la version paresseuse de @ andasa.
public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton() { } public static Singleton Instance() { return instance; } }