Die Verwendung this
in Sperranweisungen ist eine schlechte Form, da Sie im Allgemeinen nicht die Kontrolle darüber haben, wer dieses Objekt sonst möglicherweise sperrt.
Um Paralleloperationen richtig planen zu können, sollte besonders darauf geachtet werden, mögliche Deadlock-Situationen zu berücksichtigen, und eine unbekannte Anzahl von Lock-Einstiegspunkten behindert dies. Zum Beispiel kann jeder, der auf das Objekt verweist, es sperren, ohne dass der Objektdesigner / -ersteller davon erfährt. Dies erhöht die Komplexität von Multithread-Lösungen und kann deren Richtigkeit beeinträchtigen.
Ein privates Feld ist normalerweise eine bessere Option, da der Compiler Zugriffsbeschränkungen erzwingt und den Sperrmechanismus kapselt. Die Verwendung this
verletzt die Kapselung, indem ein Teil Ihrer Sperrimplementierung der Öffentlichkeit zugänglich gemacht wird. Es ist auch nicht klar, dass Sie eine Sperre erwerben, this
es sei denn, dies wurde dokumentiert. Selbst dann ist es nicht optimal, sich auf die Dokumentation zu verlassen, um ein Problem zu vermeiden.
Schließlich gibt es das verbreitete Missverständnis, dass lock(this)
das als Parameter übergebene Objekt tatsächlich geändert und in gewisser Weise schreibgeschützt oder unzugänglich gemacht wird. Das ist falsch . Das als Parameter übergebene Objekt lock
dient lediglich als Schlüssel . Wenn dieser Schlüssel bereits gesperrt ist, kann das Schloss nicht hergestellt werden. Andernfalls ist die Sperre zulässig.
Aus diesem Grund ist es schlecht, Zeichenfolgen als Schlüssel in lock
Anweisungen zu verwenden, da sie unveränderlich sind und von Teilen der Anwendung gemeinsam genutzt werden können. Sie sollten stattdessen eine private Variable verwenden, eine Object
Instanz funktioniert gut.
Führen Sie den folgenden C # -Code als Beispiel aus.
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);
}
}
Konsolenausgabe
'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)
ist eine Standardempfehlung. Es ist wichtig zu beachten, dass dies im Allgemeinen unmöglich macht, dass externer Code die mit dem Objekt verknüpfte Sperre zwischen Methodenaufrufen aufhält. Dies kann eine gute Sache sein oder auch nicht . Es besteht eine gewisse Gefahr, dass externer Code eine Sperre für eine beliebige Dauer hält, und Klassen sollten im Allgemeinen so gestaltet sein, dass eine solche Verwendung unnötig wird, aber es gibt nicht immer praktische Alternativen. Als einfaches Beispiel, es sei denn, eine Sammlung implementiert eineToArray
oderToList
eine eigene Methode ...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
- Ich glaube, diese Gespräche beziehen sich auf das SyncBlock-Bit im CLR-Objekt, also ist dies formal richtig - sperren Sie das modifizierte Objekt selbstDenn wenn Personen auf Ihren Objektinstanzzeiger (dh Ihren
this
Zeiger) zugreifen können, können sie auch versuchen, dasselbe Objekt zu sperren. Jetzt sind sie sich möglicherweise nicht bewusst, dass Siethis
intern gesperrt sind, sodass dies zu Problemen führen kann (möglicherweise zu einem Deadlock).Darüber hinaus ist es auch eine schlechte Praxis, weil es "zu viel" sperrt.
Beispielsweise könnten Sie eine Mitgliedsvariable von haben
List<int>
, und das einzige, was Sie tatsächlich sperren müssen, ist diese Mitgliedsvariable. Wenn Sie das gesamte Objekt in Ihren Funktionen sperren, werden andere Dinge, die diese Funktionen aufrufen, blockiert und warten auf die Sperre. Wenn diese Funktionen nicht auf die Mitgliederliste zugreifen müssen, wird anderer Code warten und Ihre Anwendung ohne Grund verlangsamen.quelle
Schauen Sie sich das MSDN Topic Thread Synchronization (C # -Programmierhandbuch) an.
quelle
Ich weiß, dass dies ein alter Thread ist, aber da die Leute immer noch nachschlagen und sich darauf verlassen können, scheint es wichtig, darauf hinzuweisen, dass dies
lock(typeof(SomeObject))
erheblich schlimmer ist alslock(this)
. Nachdem ich das gesagt habe; Ein aufrichtiges Lob an Alan für den Hinweis, dass dieslock(typeof(SomeObject))
eine schlechte Praxis ist.Eine Instanz von
System.Type
ist eines der allgemeinsten, grobkörnigsten Objekte, die es gibt. Zumindest ist eine Instanz von System.Type für eine AppDomain global, und .NET kann mehrere Programme in einer AppDomain ausführen. Dies bedeutet, dass zwei völlig unterschiedliche Programme möglicherweise gegenseitig Interferenzen verursachen können, selbst wenn sie einen Deadlock verursachen, wenn beide versuchen, eine Synchronisationssperre für dieselbe Instanz zu erhalten.Ist
lock(this)
also keine besonders robuste Form, kann Probleme verursachen und sollte aus allen genannten Gründen immer die Augenbrauen hochziehen. Es gibt jedoch weit verbreiteten, relativ angesehenen und anscheinend stabilen Code wie log4net, der das Sperrmuster (dieses Muster) ausgiebig verwendet, obwohl ich es persönlich vorziehen würde, wenn sich dieses Muster ändert.Aber
lock(typeof(SomeObject))
eröffnet eine ganz neue und verbesserte Dose Würmer.Für was es wert ist.
quelle
... und genau die gleichen Argumente gelten auch für dieses Konstrukt:
quelle
lock(this)
besonders logisch und prägnant erscheint. Es ist eine schrecklich grobe Sperre, und jeder andere Code kann Ihr Objekt sperren und möglicherweise Störungen in Ihrem internen Code verursachen. Nehmen Sie detailliertere Schlösser und übernehmen Sie eine strengere Kontrolle. Waslock(this)
es zu bieten hat, ist, dass es viel besser ist alslock(typeof(SomeObject))
.Stellen Sie sich vor, Sie haben eine qualifizierte Sekretärin in Ihrem Büro, die eine gemeinsame Ressource in der Abteilung ist. Hin und wieder eilen Sie auf sie zu, weil Sie eine Aufgabe haben, nur um zu hoffen, dass ein anderer Ihrer Mitarbeiter sie noch nicht beansprucht hat. Normalerweise müssen Sie nur eine kurze Zeit warten.
Da Pflege wichtig ist, entscheidet Ihr Manager, dass Kunden die Sekretärin auch direkt nutzen können. Dies hat jedoch einen Nebeneffekt: Ein Kunde kann sie sogar beanspruchen, während Sie für diesen Kunden arbeiten, und Sie benötigen sie auch, um einen Teil der Aufgaben auszuführen. Ein Deadlock tritt auf, weil das Beanspruchen keine Hierarchie mehr ist. Dies hätte insgesamt vermieden werden können, indem Kunden überhaupt nicht in Anspruch genommen werden konnten.
lock(this)
ist schlecht wie wir gesehen haben. Ein externes Objekt kann das Objekt sperren, und da Sie nicht steuern, wer die Klasse verwendet, kann jeder es sperren ... Dies ist das genaue Beispiel wie oben beschrieben. Wieder besteht die Lösung darin, die Belichtung des Objekts zu begrenzen. Wenn Sie jedoch einprivate
,protected
oderinternal
Klasse , die Sie konnten bereits kontrollieren , wer auf dem Objekt blockiert , weil Sie sicher sind , dass Sie den Code selbst geschrieben haben. Die Botschaft hier lautet also: Setzen Sie es nicht als auspublic
. Wenn Sie sicherstellen, dass in ähnlichen Szenarien eine Sperre verwendet wird, werden Deadlocks vermieden.Das genaue Gegenteil davon ist das Sperren von Ressourcen, die in der gesamten App-Domäne gemeinsam genutzt werden - im schlimmsten Fall. Es ist, als würde man seine Sekretärin nach draußen bringen und allen da draußen erlauben, sie zu beanspruchen. Das Ergebnis ist völliges Chaos - oder in Bezug auf den Quellcode: Es war eine schlechte Idee; wirf es weg und fang von vorne an. Wie machen wir das?
Typen werden in der App-Domäne geteilt, wie die meisten Leute hier betonen. Aber es gibt noch bessere Dinge, die wir verwenden können: Strings. Der Grund ist, dass Zeichenfolgen zusammengefasst werden . Mit anderen Worten: Wenn Sie zwei Zeichenfolgen mit demselben Inhalt in einer App-Domäne haben, besteht die Möglichkeit, dass sie genau denselben Zeiger haben. Da der Zeiger als Sperrschlüssel verwendet wird, erhalten Sie im Grunde ein Synonym für "Vorbereitung auf undefiniertes Verhalten".
Ebenso sollten Sie WCF-Objekte, HttpContext.Current, Thread.Current, Singletons (im Allgemeinen) usw. nicht sperren. Der einfachste Weg, dies alles zu vermeiden?
private [static] object myLock = new object();
quelle
Das Sperren dieses Zeigers kann schlecht sein, wenn Sie eine gemeinsam genutzte Ressource sperren . Eine gemeinsam genutzte Ressource kann eine statische Variable oder eine Datei auf Ihrem Computer sein, dh etwas, das von allen Benutzern der Klasse gemeinsam genutzt wird. Der Grund dafür ist, dass dieser Zeiger bei jeder Instanziierung Ihrer Klasse einen anderen Verweis auf einen Speicherort enthält. Also, über Sperren dies in einmal Instanz einer Klasse ist anders als über Sperren dies in einer anderen Instanz einer Klasse.
Schauen Sie sich diesen Code an, um zu sehen, was ich meine. Fügen Sie Ihrem Hauptprogramm in einer Konsolenanwendung den folgenden Code hinzu:
Erstellen Sie eine neue Klasse wie die folgende.
Hier ist ein Durchlauf des Programms Verriegelungs auf diesem .
Hier ist eine Ausführung des Programms, das myLock sperrt .
quelle
Random rand = new Random();
NVM verwenden. Ich glaube, ich sehe das wiederholte GleichgewichtEs gibt einen sehr guten Artikel darüber http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects von Rico Mariani, Leistungsarchitekt für die Microsoft® .NET-Laufzeit
Auszug:
quelle
Hier gibt es auch einige gute Diskussionen darüber: Ist dies die richtige Verwendung eines Mutex?
quelle
Hier ist eine viel einfachere Darstellung (aus Frage 34 hier ), warum Sperren (dies) schlecht sind und zu Deadlocks führen können, wenn Verbraucher Ihrer Klasse auch versuchen, das Objekt zu sperren. Unten kann nur einer von drei Threads fortgesetzt werden, die anderen beiden sind blockiert.
Um dies zu umgehen, verwendete dieser Typ Thread.TryMonitor (mit Timeout) anstelle von lock:
https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks
quelle
SomeClass
erhalte ich immer noch den gleichen Deadlock , wenn ich die Sperre (diese) durch eine Sperre für ein privates Instanzmitglied von ersetze . Wenn die Sperre in der Hauptklasse für ein anderes privates Instanzmitglied von Program durchgeführt wird, tritt dieselbe Sperre auf. Ich bin mir also nicht sicher, ob diese Antwort nicht irreführend und falsch ist. Sehen Sie dieses Verhalten hier: dotnetfiddle.net/DMrU5hDenn jeder Codeabschnitt, der die Instanz Ihrer Klasse sehen kann, kann diese Referenz auch sperren. Sie möchten Ihr Sperrobjekt ausblenden (kapseln), damit nur Code, der darauf verweisen muss, darauf verweisen kann. Das Schlüsselwort, auf das sich dies bezieht, bezieht sich auf die aktuelle Klasseninstanz, sodass eine beliebige Anzahl von Dingen darauf verweisen und es für die Thread-Synchronisierung verwenden kann.
Dies ist schlecht, da ein anderer Codeabschnitt die Klasseninstanz zum Sperren verwenden und möglicherweise verhindern kann, dass Ihr Code rechtzeitig gesperrt wird, oder andere Probleme bei der Thread-Synchronisierung verursachen kann. Bester Fall: Nichts anderes verwendet einen Verweis auf Ihre Klasse zum Sperren. Mittlerer Fall: Etwas verwendet einen Verweis auf Ihre Klasse, um Sperren durchzuführen, und es verursacht Leistungsprobleme. Der schlimmste Fall: Etwas verwendet eine Referenz Ihrer Klasse, um Sperren durchzuführen, und es verursacht wirklich schlimme, wirklich subtile, wirklich schwer zu debuggende Probleme.
quelle
Tut mir leid, aber ich kann dem Argument nicht zustimmen, dass das Sperren zu einem Deadlock führen könnte. Sie verwechseln zwei Dinge: Deadlocking und Hunger.
Hier ist ein Bild, das den Unterschied veranschaulicht.
Fazit
Sie können es trotzdem sicher verwenden,
lock(this)
wenn der Thread-Mangel für Sie kein Problem darstellt. Sie müssen immer noch bedenken, dass wenn der Faden, der mit Fäden hungert,lock(this)
in einem Schloss endet, in dem Ihr Objekt gesperrt ist, er schließlich in ewigem Hunger endet;)quelle
lock(this)
- diese Art von Code ist einfach falsch. Ich denke nur, es als Deadlocking zu bezeichnen, ist ein wenig missbräuchlich.Bitte lesen Sie den folgenden Link, der erklärt, warum Sperren (dies) keine gute Idee ist.
http://blogs.msdn.com/b/bclteam/archive/2004/01/20/60719.aspx
Die Lösung besteht also darin, der Klasse ein privates Objekt hinzuzufügen, z. B. lockObject, und den Codebereich wie unten gezeigt in die lock-Anweisung einzufügen:
quelle
Hier ist ein Beispielcode, der einfacher zu befolgen ist (IMO): (Funktioniert in LinqPad und verweist auf folgende Namespaces: System.Net und System.Threading.Tasks)
Zu beachten ist, dass lock (x) im Grunde genommen syntaktischer Zucker ist und Monitor.Enter verwendet und dann try, catch, finally block verwendet, um Monitor.Exit aufzurufen. Siehe: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (Abschnitt "Anmerkungen")
Ausgabe
Beachten Sie, dass Thread Nr. 12 niemals endet, wenn er tot gesperrt ist.
quelle
DoWorkUsingThisLock
Thread nicht notwendig ist, um das Problem zu veranschaulichen?Sie können eine Regel festlegen, die besagt, dass eine Klasse Code haben kann, der 'this' oder jedes Objekt sperrt, das der Code in der Klasse instanziiert. Es ist also nur dann ein Problem, wenn das Muster nicht befolgt wird.
Wenn Sie sich vor Code schützen möchten, der diesem Muster nicht folgt, ist die akzeptierte Antwort korrekt. Aber wenn das Muster befolgt wird, ist es kein Problem.
Der Vorteil von lock (this) ist die Effizienz. Was ist, wenn Sie ein einfaches "Wertobjekt" haben, das einen einzelnen Wert enthält? Es ist nur ein Wrapper und wird millionenfach instanziiert. Indem Sie die Erstellung eines privaten Synchronisierungsobjekts nur zum Sperren benötigen, haben Sie die Größe des Objekts und die Anzahl der Zuordnungen verdoppelt. Wenn es auf die Leistung ankommt, ist dies ein Vorteil.
Wenn Sie sich nicht um die Anzahl der Zuordnungen oder den Speicherbedarf kümmern, ist es aus den in anderen Antworten angegebenen Gründen vorzuziehen, die Sperre (dies) zu vermeiden.
quelle
Es tritt ein Problem auf, wenn auf die Instanz öffentlich zugegriffen werden kann, da möglicherweise andere Anforderungen dieselbe Objektinstanz verwenden. Es ist besser, private / statische Variablen zu verwenden.
quelle