Lorsque vous modifiez des données en C#, quelque chose qui ressemble à une seule opération peut être compilé en plusieurs instructions. Prenez la classe suivante:
public class Number {
private int a = 0;
public void Add(int b) {
a += b;
}
}
Lorsque vous construisez, vous obtenez le code IL suivant:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: dup
// Pushes the value of the private variable 'a' onto the stack
IL_0003: ldfld int32 Simple.Number::a
// Pushes the value of the argument 'b' onto the stack
IL_0008: ldarg.1
// Adds the top two values of the stack together
IL_0009: add
// Sets 'a' to the value on top of the stack
IL_000a: stfld int32 Simple.Number::a
IL_000f: ret
Maintenant, supposons que vous avez un objet Number
et deux threads appellent sa méthode Add
comme ceci:
number.Add(2); // Thread 1
number.Add(3); // Thread 2
Si vous voulez que le résultat soit 5 (0 + 2 + 3), il y a un problème. Vous ne savez pas quand ces threads vont exécuter leurs instructions. Les deux threads peuvent exécuter IL_0003
(pousser zéro sur la pile) avant soit exécute IL_000a
(réellement changer la variable membre) et vous obtenez ceci:
a = 0 + 2; // Thread 1
a = 0 + 3; // Thread 2
Le dernier fil pour finir « gagne » et à la fin du processus , a
est 2 ou 3 au lieu de 5.
Vous devez donc vous assurer qu'un ensemble complet d'instructions se termine avant l'autre ensemble.Pour ce faire, vous pouvez:
1) Verrouiller l'accès au membre de la classe alors qu'il est en cours d'écriture, en utilisant l'un des nombreux .NET synchronization primitives (comme lock
, Mutex
, ReaderWriterLockSlim
, etc.), de sorte qu'un seul thread peut travailler dessus à la fois.
2) Poussez les opérations d'écriture dans une file d'attente et traitez cette file avec un seul thread. Comme le souligne Thorarin, vous devez toujours synchroniser l'accès à la file d'attente si ce n'est pas sûr pour les threads, mais cela vaut la peine pour les opérations d'écriture complexes.
Il existe d'autres techniques. Certains (comme Interlocked
) sont limités à des types de données particuliers, et il y en a encore plus (comme ceux discutés dans Non-blocking synchronization et Part 4 of Joseph Albahari's Threading in C#), bien qu'ils soient plus complexes: approchez-les avec prudence.
+1 pour ReadWriterLockSlim. Cependant, je pense que le PO doit se familiariser davantage avec la programmation multi-thread avant de saisir pleinement ce concept. En ce qui concerne votre deuxième point: vous devrez synchroniser la file d'attente, compliquant simplement votre problème. – Thorarin
Assez vrai, et votre réponse fournit une introduction bien supérieure à ces concepts (bien que j'essaie de prendre un virement différent dans une mise à jour). A propos de la file d'attente, c'est vrai qu'il faut aussi la synchroniser, bien qu'il y ait des cas où cette approche simplifie les choses plutôt que de les compliquer! –