Comme d'autres réponses l'ont souligné, non, ++ n'est pas "threadsafe".
Je pense qu'il est utile, à mesure que vous apprenez à connaître le multithreading et ses dangers, de commencer à être très précis sur ce que vous entendez par "threadsafe", car différentes personnes entendent différentes choses par là. Essentiellement, l'aspect de la threadsafe qui vous intéresse ici est de savoir si l'opération est atomique ou non. Une opération "atomique" est une opération dont il est garanti qu'elle n'est pas à moitié terminée lorsqu'elle est interrompue par un autre thread.
(Il y a beaucoup d'autres problèmes de threading qui n'ont rien à voir avec l'atomicité mais qui peuvent tout de même correspondre aux définitions de la sécurité des threads de certaines personnes. Par exemple, étant donné que deux threads mutent chacun une variable, et que deux threads lisent chacun la variable, les deux lecteurs sont-ils garantis de se mettre d'accord sur la variable commander dans lequel les deux autres fils ont fait des mutations ? Si votre logique dépend de cela, alors vous avez un problème de thread safety très difficile à gérer, même si chaque lecture et écriture est atomique).
En C#, pratiquement rien n'est garanti comme étant atomique. Brièvement :
- lecture d'un entier ou d'un flottant de 32 bits
- lecture d'une référence
- écriture d'un entier de 32 bits ou d'un flottant
- rédiger une référence
sont garanties comme étant atomiques (voir la spécification pour les détails exacts).
En particulier, la lecture et l'écriture d'un entier ou d'un flottant de 64 bits est no garantie d'être atomique. Si vous dites :
C.x = 0xDEADBEEF00000000;
sur un fil, et
C.x = 0x000000000BADF00D;
sur un autre fil, alors il est possible de le faire sur un troisième fil :
Console.WriteLine(C.x);
faire en sorte que cela écrive 0xDEADBEEF0BADF00D, même si logiquement la variable n'a jamais eu cette valeur. Le langage C# se réserve le droit de faire en sorte que l'écriture dans un long soit équivalente à l'écriture dans deux ints, l'un après l'autre, et en pratique, certaines puces l'implémentent de cette façon. Un changement de thread après la première des écritures peut amener un lecteur à lire quelque chose d'inattendu.
En bref, ne partagez pas tout ce qui est entre deux fils sans verrouiller quelque chose. Les verrous ne sont lents que lorsqu'ils sont satisfaits ; si vous avez un problème de performance à cause de verrous contestés, alors réparer le défaut d'architecture qui conduit à des serrures contestées . Si les verrous ne sont pas contestés et sont encore trop lents, ce n'est qu'à ce moment-là que vous devez envisager de passer à des techniques dangereuses de verrouillage bas.
La technique courante de verrouillage faible à utiliser ici est bien sûr d'appeler Threading.Interlocked.Increment
qui effectue l'incrémentation d'un entier d'une manière garantie comme étant atomique. (Notez cependant qu'il n'offre toujours pas de garanties sur des choses comme ce qui se passe si deux threads effectuent chacun des incréments imbriqués de deux variables différentes à des moments différents, et que d'autres threads essaient de déterminer quel incrément s'est produit "en premier". C# ne garantit pas qu'un ordre unique et cohérent des événements soit vu par tous les threads).