C'est une mauvaise forme à utiliser this
dans les instructions de verrouillage, car il est généralement hors de votre contrôle qui d'autre pourrait verrouiller cet objet.
Afin de planifier correctement les opérations parallèles, une attention particulière doit être portée à la prise en compte d'éventuelles situations de blocage, et le fait d'avoir un nombre inconnu de points d'entrée de verrouillage empêche cela. Par exemple, toute personne ayant une référence à l'objet peut le verrouiller sans que le concepteur / créateur d'objet le sache. Cela augmente la complexité des solutions multithread et peut affecter leur exactitude.
Un champ privé est généralement une meilleure option car le compilateur lui imposera des restrictions d'accès et encapsulera le mécanisme de verrouillage. L'utilisation this
viole l'encapsulation en exposant une partie de votre implémentation de verrouillage au public. Il n'est pas clair non plus que vous obtiendrez un verrou à this
moins qu'il n'ait été documenté. Même alors, se fier à la documentation pour éviter un problème n'est pas optimal.
Enfin, il y a l'idée fausse courante qui lock(this)
modifie réellement l'objet passé en paramètre, et le rend en quelque sorte en lecture seule ou inaccessible. C'est faux . L'objet passé en paramètre lock
sert simplement de clé . Si un verrou est déjà maintenu sur cette clé, le verrouillage ne peut pas être effectué; sinon, le verrouillage est autorisé.
C'est pourquoi il est mauvais d'utiliser des chaînes comme clés dans les lock
instructions, car elles sont immuables et sont partagées / accessibles dans toutes les parties de l'application. Vous devriez utiliser une variable privée à la place, une Object
instance fera l'affaire.
Exécutez le code C # suivant comme exemple.
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public void LockThis()
{
lock (this)
{
System.Threading.Thread.Sleep(10000);
}
}
}
class Program
{
static void Main(string[] args)
{
var nancy = new Person {Name = "Nancy Drew", Age = 15};
var a = new Thread(nancy.LockThis);
a.Start();
var b = new Thread(Timewarp);
b.Start(nancy);
Thread.Sleep(10);
var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
var c = new Thread(NameChange);
c.Start(anotherNancy);
a.Join();
Console.ReadLine();
}
static void Timewarp(object subject)
{
var person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
// A lock does not make the object read-only.
lock (person.Name)
{
while (person.Age <= 23)
{
// There will be a lock on 'person' due to the LockThis method running in another thread
if (Monitor.TryEnter(person, 10) == false)
{
Console.WriteLine("'this' person is locked!");
}
else Monitor.Exit(person);
person.Age++;
if(person.Age == 18)
{
// Changing the 'person.Name' value doesn't change the lock...
person.Name = "Nancy Smith";
}
Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
}
}
}
static void NameChange(object subject)
{
var person = subject as Person;
if (person == null) throw new ArgumentNullException("subject");
// You should avoid locking on strings, since they are immutable.
if (Monitor.TryEnter(person.Name, 30) == false)
{
Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
}
else Monitor.Exit(person.Name);
if (Monitor.TryEnter("Nancy Drew", 30) == false)
{
Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
}
else Monitor.Exit("Nancy Drew");
if (Monitor.TryEnter(person.Name, 10000))
{
string oldName = person.Name;
person.Name = "Nancy Callahan";
Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
}
else Monitor.Exit(person.Name);
}
}
Sortie console
'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.
lock(this)
constitue un conseil standard; il est important de noter que cela rend généralement impossible pour le code extérieur de maintenir le verrou associé à l'objet entre les appels de méthode. Cela peut ou non être une bonne chose . Il y a un certain danger à permettre au code extérieur de maintenir un verrou pendant une durée arbitraire, et les classes devraient généralement être conçues de manière à rendre une telle utilisation inutile, mais il n'y a pas toujours d'alternatives pratiques. À titre d'exemple simple, à moins qu'une collectionToArray
ToList
there is the common misconception that lock(this) actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false
- Je crois que ces discussions portent sur le bit SyncBlock dans un objet CLR, donc formellement c'est vrai - verrouiller l'objet modifié luiParce que si les gens peuvent accéder à votre instance d'objet (c'est-à-dire votre
this
pointeur), ils peuvent également essayer de verrouiller ce même objet. Maintenant, ils ne savent peut-être pas que vous vous verrouillez enthis
interne, donc cela peut causer des problèmes (peut-être un blocage)En plus de cela, c'est aussi une mauvaise pratique, car il se verrouille "trop"
Par exemple, vous pouvez avoir une variable membre de
List<int>
, et la seule chose que vous devez réellement verrouiller est cette variable membre. Si vous verrouillez l'intégralité de l'objet dans vos fonctions, les autres éléments qui appellent ces fonctions seront bloqués en attendant le verrouillage. Si ces fonctions n'ont pas besoin d'accéder à la liste des membres, vous allez faire attendre un autre code et ralentir votre application sans aucune raison.la source
Jetez un oeil à la synchronisation des threads de rubrique MSDN (Guide de programmation C #)
la source
Je sais que c'est un vieux fil conducteur, mais parce que les gens peuvent toujours le chercher et s'y fier, il semble important de souligner que
lock(typeof(SomeObject))
c'est bien pire quelock(this)
. Ayant dit cela; bravo sincère à Alan pour avoir souligné quelock(typeof(SomeObject))
c'est une mauvaise pratique.Une instance de
System.Type
est l'un des objets les plus génériques à grain grossier qui existe. À tout le moins, une instance de System.Type est globale pour un AppDomain et .NET peut exécuter plusieurs programmes dans un AppDomain. Cela signifie que deux programmes entièrement différents pourraient potentiellement provoquer des interférences l'un dans l'autre, même au point de créer un blocage s'ils tentent tous les deux d'obtenir un verrou de synchronisation sur la même instance de type.Ce
lock(this)
n'est donc pas une forme particulièrement robuste, peut causer des problèmes et devrait toujours lever les sourcils pour toutes les raisons citées. Pourtant, il existe un code largement utilisé, relativement bien respecté et apparemment stable comme log4net qui utilise largement le modèle de verrouillage (ce), même si je préférerais personnellement que ce modèle change.Mais
lock(typeof(SomeObject))
ouvre une toute nouvelle boîte de vers améliorée.Pour ce que ça vaut.
la source
... et les mêmes arguments exacts s'appliquent également à cette construction:
la source
lock(this)
semble particulièrement logique et succinct. C'est un verrou terriblement grossier, et tout autre code pourrait verrouiller votre objet, ce qui pourrait provoquer des interférences dans votre code interne. Prenez des verrous plus granuleux et prenez un contrôle plus strict. Celock(this)
qui est vrai, c'est que c'est beaucoup mieux quelock(typeof(SomeObject))
.Imaginez que vous ayez une secrétaire qualifiée à votre bureau qui soit une ressource partagée au sein du ministère. De temps en temps, vous vous précipitez vers eux parce que vous avez une tâche, seulement pour espérer qu'un autre de vos collègues ne les a pas déjà réclamés. Habituellement, vous n'avez qu'à attendre un court instant.
Parce que se soucier c'est partager, votre manager décide que les clients peuvent également utiliser directement la secrétaire. Mais cela a un effet secondaire: un client peut même les réclamer pendant que vous travaillez pour ce client et vous en avez également besoin pour exécuter une partie des tâches. Un blocage se produit, car la revendication n'est plus une hiérarchie. Cela aurait pu être évité tous ensemble en ne permettant pas aux clients de les réclamer en premier lieu.
lock(this)
est mauvais comme nous l'avons vu. Un objet extérieur peut se verrouiller sur l'objet et puisque vous ne contrôlez pas qui utilise la classe, tout le monde peut le verrouiller ... C'est l'exemple exact décrit ci-dessus. Encore une fois, la solution est de limiter l'exposition de l'objet. Toutefois, si vous avezprivate
,protected
ouinternal
classe que vous pouvez déjà contrôler qui est verrouillage sur votre objet , parce que vous êtes sûr que vous avez écrit votre code vous - même. Donc le message ici est: ne l'exposez pas en tant quepublic
. De plus, garantir qu'un verrou est utilisé dans des scénarios similaires évite les blocages.L'opposé complet de cela est de verrouiller les ressources partagées dans tout le domaine d'application - le pire des cas. C'est comme mettre votre secrétaire dehors et permettre à tout le monde de les réclamer. Le résultat est un chaos total - ou en termes de code source: c'était une mauvaise idée; jetez-le et recommencez. Alors, comment on fait ça?
Les types sont partagés dans le domaine de l'application, comme le soulignent la plupart des gens ici. Mais il y a encore de meilleures choses que nous pouvons utiliser: les chaînes. La raison en est que les chaînes sont regroupées . En d'autres termes: si vous avez deux chaînes qui ont le même contenu dans un domaine d'application, il est possible qu'elles aient exactement le même pointeur. Puisque le pointeur est utilisé comme clé de verrouillage, ce que vous obtenez essentiellement est un synonyme de "se préparer à un comportement indéfini".
De même, vous ne devriez pas verrouiller les objets WCF, HttpContext.Current, Thread.Current, Singletons (en général), etc. La manière la plus simple d'éviter tout cela?
private [static] object myLock = new object();
la source
Le verrouillage du pointeur this peut être mauvais si vous verrouillez une ressource partagée . Une ressource partagée peut être une variable statique ou un fichier sur votre ordinateur - c'est-à-dire quelque chose qui est partagé entre tous les utilisateurs de la classe. La raison en est que le pointeur this contiendra une référence différente à un emplacement en mémoire chaque fois que votre classe est instanciée. Ainsi, le verrouillage sur cette instance unique d'une classe est différent de celui sur une autre instance d'une classe.
Consultez ce code pour voir ce que je veux dire. Ajoutez le code suivant à votre programme principal dans une application console:
Créez une nouvelle classe comme ci-dessous.
Voici une exécution du programme de verrouillage sur ce point .
Voici une exécution du programme de verrouillage sur myLock .
la source
Random rand = new Random();
nvm, je pense que je vois son équilibre répétéIl y a un très bon article à ce sujet http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects par Rico Mariani, architecte de la performance pour le runtime Microsoft® .NET
Extrait:
la source
Il y a aussi une bonne discussion à ce sujet ici: Est-ce la bonne utilisation d'un mutex?
la source
Voici une illustration beaucoup plus simple (tirée de la question 34 ici ) pourquoi le verrouillage (ceci) est mauvais et peut entraîner des blocages lorsque le consommateur de votre classe essaie également de verrouiller l'objet. Ci-dessous, un seul des trois threads peut continuer, les deux autres sont bloqués.
Pour contourner ce problème, ce type a utilisé Thread.TryMonitor (avec timeout) au lieu de verrouiller:
https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks
la source
SomeClass
, j'obtiens toujours le même blocage. En outre, si le verrouillage de la classe principale est effectué sur un autre membre d'instance privée de Program, le même verrouillage se produit. Donc, je ne sais pas si cette réponse n'est pas trompeuse et incorrecte. Voir ce comportement ici: dotnetfiddle.net/DMrU5hParce que tout morceau de code qui peut voir l'instance de votre classe peut également verrouiller cette référence. Vous souhaitez masquer (encapsuler) votre objet de verrouillage afin que seul le code qui doit le référencer puisse le référencer. Le mot-clé this fait référence à l'instance de classe actuelle, donc un certain nombre de choses pourraient y faire référence et pourraient l'utiliser pour effectuer la synchronisation des threads.
Pour être clair, cela est mauvais car un autre morceau de code pourrait utiliser l'instance de classe pour verrouiller et pourrait empêcher votre code d'obtenir un verrouillage en temps opportun ou pourrait créer d'autres problèmes de synchronisation de thread. Meilleur cas: rien d'autre n'utilise une référence à votre classe pour se verrouiller. Cas du milieu: quelque chose utilise une référence à votre classe pour faire des verrous et cela cause des problèmes de performances. Pire cas: quelque chose utilise une référence de votre classe pour faire des verrous et cela cause des problèmes vraiment mauvais, vraiment subtils, vraiment difficiles à déboguer.
la source
Désolé les gars, mais je ne suis pas d'accord avec l'argument selon lequel le verrouillage pourrait entraîner un blocage. Vous confondez deux choses: l'impasse et la faim.
Voici une image qui illustre la différence.
Conclusion
Vous pouvez toujours utiliser en toute sécurité
lock(this)
si la famine de fil n'est pas un problème pour vous. Vous devez toujours garder à l'esprit que lorsque le fil, qui utilise le fil affamé en utilisant deslock(this)
extrémités dans une serrure ayant votre objet verrouillé, il se terminera finalement par une famine éternelle;)la source
lock(this)
- ce type de code est tout simplement faux. Je pense simplement que le qualifier d'impasse est un peu abusif.Veuillez vous référer au lien suivant qui explique pourquoi verrouiller (ceci) n'est pas une bonne idée.
http://blogs.msdn.com/b/bclteam/archive/2004/01/20/60719.aspx
La solution consiste donc à ajouter un objet privé, par exemple, lockObject à la classe et à placer la région de code à l'intérieur de l'instruction lock, comme indiqué ci-dessous:
la source
Voici un exemple de code plus simple à suivre (IMO): (fonctionnera dans LinqPad , référencera les espaces de noms suivants: System.Net et System.Threading.Tasks)
Quelque chose à retenir est que lock (x) est fondamentalement du sucre syntaxique et ce qu'il fait est d'utiliser Monitor.Enter puis utilise un try, catch, finalement block pour appeler Monitor.Exit. Voir: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (section remarques)
Production
Notez que le thread # 12 ne se termine jamais car il est verrouillé.
la source
DoWorkUsingThisLock
fil n'est pas nécessaire pour illustrer le problème?Vous pouvez établir une règle qui dit qu'une classe peut avoir du code qui se verrouille sur «ceci» ou tout autre objet que le code de la classe instancie. Ce n'est donc un problème que si le modèle n'est pas suivi.
Si vous souhaitez vous protéger d'un code qui ne suivra pas ce modèle, la réponse acceptée est correcte. Mais si le modèle est suivi, ce n'est pas un problème.
L'avantage de la serrure (ceci) est l'efficacité. Que faire si vous avez un simple "objet valeur" qui contient une seule valeur. Ce n'est qu'un emballage, et il est instancié des millions de fois. En exigeant la création d'un objet de synchronisation privé uniquement pour le verrouillage, vous avez essentiellement doublé la taille de l'objet et doublé le nombre d'allocations. Lorsque les performances sont importantes, c'est un avantage.
Lorsque vous ne vous souciez pas du nombre d'allocations ou de l'encombrement de la mémoire, éviter le verrouillage (ceci) est préférable pour les raisons indiquées dans d'autres réponses.
la source
Il y aura un problème si l'instance est accessible publiquement car il pourrait y avoir d'autres demandes qui pourraient utiliser la même instance d'objet. Il est préférable d'utiliser une variable privée / statique.
la source