Je suis de la conception d'une classe que je souhaite faire en lecture seule après un thread principal est fait de la configuration, c'est à dire de "geler". Eric Lippert appelle cette popsicle immuabilité. Après il est congelé, il peut être accédé par plusieurs threads en même temps pour la lecture.
Ma question est de savoir comment écrire un thread-safe manière réaliste efficace, c'est à dire sans essayer d'être inutilement intelligent.
Tentative 1:
public class Foobar
{
private Boolean _isFrozen;
public void Freeze() { _isFrozen = true; }
// Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
public void WriteValue(Object val)
{
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
public Object ReadSomething()
{
return it;
}
}
Eric Lippert semble suggérer ce serait OK dans cepost. Je sais que les écritures sont la libération de la sémantique, mais autant je comprends cela s'applique uniquement à la commande, et il ne veut pas nécessairement dire que tous les threads vont voir la valeur immédiatement après l'écriture. Quelqu'un peut-il confirmer cela? Cela signifierait que cette solution n'est pas thread-safe (cela peut ne pas être la seule raison, bien sûr).
Tentative 2:
Ci-dessus, mais en utilisant Interlocked.Exchange
afin de s'assurer que la valeur est en fait publié:
public class Foobar
{
private Int32 _isFrozen;
public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
public void WriteValue(Object val)
{
if (_isFrozen == 1)
throw new InvalidOperationException();
// write ...
}
}
L'avantage ici serait de nous assurer que la valeur est publiée à l'insu de la souffrance le dessus sur tous les lire. Si aucune de ces lectures sont déplacés avant l'écriture de _isFrozen comme le Contrefil méthode utilise une mémoire pleine de barrière, je suppose que c'est thread-safe. Cependant, qui sait ce que le compilateur va faire (et conformément à la section 3.10 du C# spec qui semble comme beaucoup), donc je ne sais pas si c'est thread-safe.
Tentative 3:
Également faire de la lecture à l'aide de Interlocked
.
public class Foobar
{
private Int32 _isFrozen;
public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
public void WriteValue(Object val)
{
if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
throw new InvalidOperationException();
// write ...
}
}
Certainement pas thread-safe, mais il semble un peu fastidieux de faire l'échange de comparaison pour tous les lire. Je sais que cette surcharge est probablement minime, mais je suis à la recherche d'un raisonnablement efficace méthode (bien que peut-être ce qu'il est).
Tentative 4:
À l'aide de volatile
:
public class Foobar
{
private volatile Boolean _isFrozen;
public void Freeze() { _isFrozen = true; }
public void WriteValue(Object val)
{
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
}
Mais Joe Duffy a déclaré "sayonara volatile", donc je ne vais pas envisager cette solution.
Tentative de 5:
Verrouiller tout, semble un peu exagéré:
public class Foobar
{
private readonly Object _syncRoot = new Object();
private Boolean _isFrozen;
public void Freeze() { lock(_syncRoot) _isFrozen = true; }
public void WriteValue(Object val)
{
lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
}
Aussi semble définitivement thread-safe, mais a plus de généraux que d'utiliser le Contrefil approche ci-dessus, donc je serais favorable à tenter 3 sur celui.
Et puis je peux venir avec au moins un peu plus (je suis sûr qu'il ya beaucoup d'autres):
Tentative de 6: utilisation Thread.VolatileWrite
et Thread.VolatileRead
, mais ce sont des soi-disant un peu sur le côté le plus lourd.
Tentative de 7: utilisation Thread.MemoryBarrier
, me semble un peu trop interne.
Tentative 8: créer un immuable la copie ne veux pas faire ce
Résumant:
- qui tentent utilisez-vous et pourquoi (ou comment auriez-vous fait si totalement différent)? (c'est à dire quel est le meilleur moyen pour la publication d'une valeur une fois qui est ensuite lu simultanément, tout en étant raisonnablement efficace sans être trop "sage"?)
- n' .NET de la mémoire du modèle de la "libération" de la sémantique de l'écrit implique que tous les autres threads voir les mises à jour (de cohérence de cache, etc.)? En général, je ne veux pas trop penser à cela, mais c'est agréable d'avoir une compréhension de.
EDIT:
Peut-être que ma question n'était pas claire, mais je suis à la recherche, en particulier pour les raisons ci-dessus tentatives sont bonnes ou mauvaises. Notez que je parle ici de l'un scénario d'un seul écrivain qui écrit puis gèle avant, toutes les lectures. Je crois tentative 1 est OK mais je voudrais savoir exactement pourquoi (je me demande si les lectures peuvent être optimisés à l'écart d'une certaine manière, par exemple). J'ai moins de soins sur si oui ou non c'est une bonne pratique de conception, mais plus sur la réelle de filetage aspect de celui-ci.
Merci beaucoup pour la réponse à la question, mais j'ai choisi de célébrer cela comme une réponse moi-même, parce que je pense que les réponses données ne sont pas tout à fait répondu à ma question et je ne veux pas donner l'impression à tous ceux qui visitent le site que la réponse est correcte simplement parce qu'il a été automatiquement marqués comme tels en raison de l'abondance d'expirer. En outre, je ne pense pas que la réponse avec le plus grand nombre de voix a été massivement voté pour, pas assez pour marquer automatiquement comme une réponse.
Je suis toujours appuyé à la tentative #1 est correcte, cependant, j'aurais aimé avoir quelques réponses faisant autorité. Je comprends x86 a un modèle fort, mais je ne veux pas (et ne devrait pas) code pour une architecture particulière, après tout c'est une des belles choses au sujet de .NET.
Si vous êtes dans le doute au sujet de la réponse, optez pour l'une de verrouillage approches, peut-être avec les optimisations présentées ici à éviter beaucoup de conflits sur la serrure.