Le thread du constructeur statique c# est-il sûr?
En d'autres termes, ce thread D'implémentation Singleton est-il sûr:
public class Singleton
{
private static Singleton instance;
private Singleton() { }
static Singleton()
{
instance = new Singleton();
}
public static Singleton Instance
{
get { return instance; }
}
}
10 réponses
Les constructeurs statiques ne peuvent être exécutés qu'une seule fois par domaine d'application, avant la création d'instances d'une classe ou l'accès à des membres statiques. http://msdn.microsoft.com/en-us/library/aa645612.aspx
L'implémentation affichée est thread safe pour la construction initiale, c'est-à-dire qu'aucun test de verrouillage ou nul n'est requis pour construire L'objet Singleton. Toutefois, cela ne signifie pas que toute utilisation de l'instance sera synchronisée. Il existe une variété de façons que cela peut être fait; j'ai montré un ci-dessous.
public class Singleton
{
private static Singleton instance;
// Added a static mutex for synchronising use of instance.
private static System.Threading.Mutex mutex;
private Singleton() { }
static Singleton()
{
instance = new Singleton();
mutex = new System.Threading.Mutex();
}
public static Singleton Acquire()
{
mutex.WaitOne();
return instance;
}
// Each call to Acquire() requires a call to Release()
public static void Release()
{
mutex.ReleaseMutex();
}
}
Bien que toutes ces réponses donnent la même réponse générale, il y a une mise en garde.
Rappelez-vous que toutes les dérivations potentielles d'une classe générique sont compilées en tant que types individuels. Soyez donc prudent lors de l'implémentation de constructeurs statiques pour les types génériques.
class MyObject<T>
{
static MyObject()
{
//this code will get executed for each T.
}
}
Modifier:
Voici la démonstration:
static void Main(string[] args)
{
var obj = new Foo<object>();
var obj2 = new Foo<string>();
}
public class Foo<T>
{
static Foo()
{
System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));
}
}
Dans la console:
Hit System.Object
Hit System.String
À l'Aide d'un constructeur statique en fait est thread-safe. Le constructeur statique est garanti pour être exécuté une seule fois.
À partir de la spécification de langage C # http://msdn.microsoft.com/en-us/library/aa645612 (VS.71).aspx :
Le constructeur statique d'une classe s'exécute au plus une fois dans un domaine d'application donné. L'exécution d'un constructeur statique est déclenchée par le premier des événements suivants qui se produisent dans un domaine d'application:
- Une instance de la classe est créée.
- Tous les membres statiques de la classe sont référencés.
Donc, oui, vous pouvez avoir confiance que votre singleton sera correctement instancié.
Zooba a fait un excellent point (et 15 secondes avant moi aussi!) que le constructeur statique ne garantira pas l'accès partagé thread-safe au singleton. Cela devra être traité d'une autre manière.
Voici la version Cliffnotes de la page MSDN ci-dessus sur C # singleton:
Utiliser le modèle suivant, toujours, vous ne pouvez pas vous tromper:
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
Au-delà des caractéristiques évidentes de singleton, il vous donne ces deux choses gratuitement (en ce qui concerne singleton en C++):
- construction paresseuse (ou pas de construction si elle n'a jamais été appelée)
- synchronisation
Les constructeurs statiques ne sont garantis qu'une seule fois par Domaine D'application, donc votre approche devrait être correcte. Cependant, il n'est fonctionnellement pas différent de la version en ligne plus concise:
private static readonly Singleton instance = new Singleton();
La sécurité des threads est plus problématique lorsque vous initialisez des choses paresseusement.
La spécification Common Language Infrastructure garantit que " un initialiseur de type doit s'exécuter exactement une fois pour un type donné, sauf s'il est explicitement appelé par le code utilisateur."(Section 9.5.3.1.) Donc, à moins que vous avez un peu il loufoque sur L'appel lâche Singleton::.cctor directement (peu probable) votre constructeur statique s'exécutera exactement une fois avant que le type Singleton ne soit utilisé, une seule instance de Singleton sera créée et votre propriété D'Instance est thread-safe.
Notez que si Le constructeur de Singleton accède à la propriété Instance (même indirectement) alors la propriété Instance sera null. Le mieux que vous pouvez faire est de détecter quand cela se produit et de lancer une exception, en vérifiant que l'instance est non nulle dans l'accesseur de propriété. Une fois votre constructeur statique terminé, la propriété D'Instance sera non-null.
Commela réponse de Zoomba indique que vous devrez rendre Singleton sûr d'accéder à partir de plusieurs threads, ou implémenter un mécanisme de verrouillage autour de l'utilisation l'instance singleton.
Juste pour être pédant, mais il n'y a pas de constructeur statique, mais plutôt des initialiseurs de type statique, Voici une petite démonstration de la dépendance du constructeur statique cyclique qui illustre ce point.
Le constructeur statique est garanti pour être thread safe. Aussi, consultez la discussion sur Singleton à DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/
Le constructeur statique terminera en cours d'exécution avant que n'importe quel thread soit autorisé à accéder à la classe.
private class InitializerTest
{
static private int _x;
static public string Status()
{
return "_x = " + _x;
}
static InitializerTest()
{
System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
_x = 1;
Thread.Sleep(3000);
_x = 2;
System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
}
}
private void ClassInitializerInThread()
{
System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
string status = InitializerTest.Status();
System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
}
private void classInitializerButton_Click(object sender, EventArgs e)
{
new Thread(ClassInitializerInThread).Start();
new Thread(ClassInitializerInThread).Start();
new Thread(ClassInitializerInThread).Start();
}
Le code ci-dessus a produit les résultats ci-dessous.
10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).
Même si le constructeur statique a mis beaucoup de temps à s'exécuter, les autres threads se sont arrêtés et ont attendu. Tous les threads lisent la valeur de _x définie en bas du constructeur statique.
Bien que d'autres réponses soient pour la plupart correctes, il y a encore une autre mise en garde avec les constructeurs statiques.
Selon la section II.10.5.3. 3 Races et blocages de la langue commune ECMA-335 Infrastructure
L'initialisation de Type seule ne doit pas créer de blocage à moins qu'un code appelé depuis un initialiseur de type (directement ou indirectement) explicitement appelle les opérations de blocage.
Le code ci-dessous dans un blocage
using System.Threading;
class MyClass
{
static void Main() { /* Won’t run... the static constructor deadlocks */ }
static MyClass()
{
Thread thread = new Thread(arg => { });
thread.Start();
thread.Join();
}
}
L'auteur Original est Igor Ostrovsky, voir son article ici .