Jest to zła forma do użycia this
w instrukcjach blokady, ponieważ generalnie nie masz kontroli, kto jeszcze mógłby zablokować ten obiekt.
Aby właściwie zaplanować operacje równoległe, należy zwrócić szczególną uwagę na możliwe sytuacje zakleszczenia, a utrudnia to nieznana liczba punktów wejścia do śluzy. Na przykład każdy, kto ma odniesienie do obiektu, może się na nim zablokować bez wiedzy projektanta / twórcy obiektu. Zwiększa to złożoność rozwiązań wielowątkowych i może wpływać na ich poprawność.
Pole prywatne jest zwykle lepszą opcją, ponieważ kompilator będzie wymuszał ograniczenia dostępu do niego i obuduje mechanizm blokujący. Używanie this
narusza enkapsulację, udostępniając publicznie część implementacji blokowania. Nie jest również jasne, że uzyskasz blokadę, this
chyba że została udokumentowana. Nawet wtedy poleganie na dokumentacji, aby zapobiec problemowi, nie jest optymalne.
Wreszcie istnieje powszechne nieporozumienie, które lock(this)
faktycznie modyfikuje obiekt przekazywany jako parametr i w pewien sposób czyni go tylko do odczytu lub niedostępnym. To jest fałsz . Obiekt przekazany jako parametr lock
służy jedynie jako klucz . Jeśli blokada jest już utrzymywana na tym kluczu, blokada nie może być wykonana; w przeciwnym razie blokada jest dozwolona.
Dlatego źle jest używać ciągów jako kluczy w lock
instrukcjach, ponieważ są one niezmienne i są udostępniane / dostępne w różnych częściach aplikacji. Zamiast tego powinieneś użyć zmiennej prywatnej, Object
instancja dobrze się sprawdzi.
Uruchom następujący kod C # jako przykład.
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);
}
}
Wyjście konsoli
'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)
standardowej porady; ważne jest, aby pamiętać, że takie postępowanie ogólnie uniemożliwi kod zewnętrzny, aby blokada związana z obiektem była wstrzymywana między wywołaniami metod. To może, ale nie musi, być dobrą rzeczą . Istnieje pewne niebezpieczeństwo zezwalające zewnętrznemu kodowi na blokadę na dowolny czas, a klasy powinny być ogólnie zaprojektowane tak, aby takie użycie było niepotrzebne, ale nie zawsze istnieją praktyczne alternatywy. Jako prosty przykład, chyba że kolekcja implementuje własną metodęToArray
lubToList
metodę ...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
- Uważam, że te rozmowy dotyczą bitu SyncBlock w obiekcie CLR, więc formalnie jest to słuszne - sam blokuj zmodyfikowany obiektPonieważ jeśli ludzie mogą dostać się do
this
wskaźnika instancji obiektu (tj. Twojego ), mogą także spróbować zablokować ten sam obiekt. Teraz mogą nie wiedzieć, że blokujesz sięthis
wewnętrznie, więc może to powodować problemy (być może impas)Oprócz tego jest to również zła praktyka, ponieważ blokuje „za dużo”
Na przykład możesz mieć zmienną składową
List<int>
, a jedyną rzeczą, którą musisz zablokować, jest zmienna składowa. Jeśli zablokujesz cały obiekt w swoich funkcjach, wówczas inne rzeczy wywołujące te funkcje zostaną zablokowane w oczekiwaniu na blokadę. Jeśli te funkcje nie będą musiały uzyskiwać dostępu do listy członków, spowoduje to, że inny kod będzie czekać i spowolnić aplikację bez żadnego powodu.źródło
Rzuć okiem na temat synchronizacji wątków MSDN (Podręcznik programowania w języku C #)
źródło
Wiem, że to stary wątek, ale ponieważ ludzie wciąż mogą to sprawdzić i polegać na nim, wydaje się ważne, aby zauważyć, że
lock(typeof(SomeObject))
jest znacznie gorzej niżlock(this)
. Powiedziawszy to; szczere podziękowania dla Alana za wskazanie, żelock(typeof(SomeObject))
jest to zła praktyka.Instancja
System.Type
jest jednym z najbardziej ogólnych, gruboziarnistych obiektów. Przynajmniej instancja System.Type jest globalna dla AppDomain, a .NET może uruchamiać wiele programów w AppDomain. Oznacza to, że dwa zupełnie różne programy mogą potencjalnie powodować wzajemne zakłócenia, nawet w stopniu powodującym zakleszczenie, jeśli oba będą próbowały uzyskać blokadę synchronizacji dla tego samego typu wystąpienia.Nie
lock(this)
jest to więc szczególnie mocna forma, może powodować problemy i zawsze powinna podnosić brwi z wszystkich wymienionych powodów. Istnieje jednak powszechnie stosowany, względnie szanowany i pozornie stabilny kod, taki jak log4net, który intensywnie używa wzorca blokady (tego), chociaż osobiście wolałbym zobaczyć zmianę tego wzorca.Ale
lock(typeof(SomeObject))
otwiera się zupełnie nowa i ulepszona puszka robaków.Tyle ile jest warte.
źródło
... i dokładnie te same argumenty dotyczą również tego konstruktu:
źródło
lock(this)
wydaje się to szczególnie logiczne i zwięzłe. Jest to strasznie zgrubna blokada, a każdy inny kod może zablokować Twój obiekt, potencjalnie powodując zakłócenia w twoim wewnętrznym kodzie. Weź więcej granularnych zamków i przejmij lepszą kontrolę. To,lock(this)
co się dzieje, jest o wiele lepsze niżlock(typeof(SomeObject))
.Wyobraź sobie, że masz wykwalifikowanego sekretarza w swoim biurze, który jest wspólnym zasobem w dziale. Raz na jakiś czas spieszycie się do nich, ponieważ macie zadanie, tylko po to, aby mieć nadzieję, że inny z waszych współpracowników jeszcze ich nie odebrał. Zwykle trzeba tylko chwilę poczekać.
Ponieważ opieka jest dzieleniem się, kierownik decyduje, że klienci mogą również bezpośrednio korzystać z sekretarki. Ma to jednak efekt uboczny: klient może nawet przejąć je, gdy pracujesz dla tego klienta i potrzebujesz go również do wykonania części zadań. Występuje impas, ponieważ roszczenia nie są już hierarchią. Można było tego uniknąć razem, nie pozwalając klientom w pierwszej kolejności dochodzić ich roszczeń.
lock(this)
jest zły, jak widzieliśmy. Obiekt zewnętrzny może zablokować obiekt, a ponieważ nie kontrolujesz, kto używa klasy, każdy może go zablokować ... To jest dokładny przykład, jak opisano powyżej. Ponownie rozwiązaniem jest ograniczenie ekspozycji obiektu. Jednakże, jeśli maszprivate
,protected
lubinternal
klasa ty mógł już kontrolować, kto jest blokowanie na obiekcie , bo jesteś pewien, że masz napisany kod siebie. Oto wiadomość: nie ujawniaj tego jakopublic
. Ponadto zapewnienie użycia blokady w podobnym scenariuszu pozwala uniknąć zakleszczeń.Całkowitym przeciwieństwem tego jest blokowanie zasobów współdzielonych w domenie aplikacji - najgorszy scenariusz. To tak, jakbyś wyrzucił swoją sekretarkę na zewnątrz i pozwolił wszystkim jej domagać się. Rezultatem jest całkowity chaos - lub jeśli chodzi o kod źródłowy: był to zły pomysł; wyrzuć to i zacznij od nowa. Jak to robimy?
Typy są udostępniane w domenie aplikacji, jak wskazuje większość osób tutaj. Ale są jeszcze lepsze rzeczy, których możemy użyć: łańcuchy. Powodem jest to, że ciągi są łączone . Innymi słowy: jeśli masz dwa ciągi, które mają tę samą zawartość w domenie aplikacji, istnieje szansa, że mają dokładnie taki sam wskaźnik. Ponieważ wskaźnik jest używany jako klucz blokady, w zasadzie otrzymujesz synonim „przygotuj się na niezdefiniowane zachowanie”.
Podobnie nie powinieneś blokować obiektów WCF, HttpContext.Current, Thread.Current, Singletons (ogólnie) itp. Najłatwiejszym sposobem na uniknięcie tego wszystkiego?
private [static] object myLock = new object();
źródło
Blokowanie tego wskaźnika może być złe, jeśli blokujesz udostępniony zasób . Współużytkowanym zasobem może być zmienna statyczna lub plik na twoim komputerze - tj. Coś, co jest współużytkowane przez wszystkich użytkowników klasy. Powodem jest to, że wskaźnik ten będzie zawierał inne odwołanie do lokalizacji w pamięci za każdym razem, gdy twoja instancja zostanie utworzona. Zatem zablokowanie tego w jednym wystąpieniu klasy różni się od zablokowania tego w innym wystąpieniu klasy.
Sprawdź ten kod, aby zobaczyć, co mam na myśli. Dodaj następujący kod do głównego programu w aplikacji konsoli:
Utwórz nową klasę jak poniżej.
Oto przebieg programu blokującego na tym .
Oto uruchomienie programu blokującego na myLock .
źródło
Random rand = new Random();
NVM, myślę, że widzę jego powtarzające się saldoJest na ten temat bardzo dobry artykuł http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects autorstwa Rico Mariani, architekta wydajności dla środowiska uruchomieniowego Microsoft® .NET
Fragment:
źródło
Jest tu również kilka dobrych dyskusji na ten temat: Czy to właściwe użycie muteksu?
źródło
Oto o wiele prostsza ilustracja (zaczerpnięta z pytania 34 tutaj ), dlaczego blokada (ta) jest zła i może powodować zakleszczenia, gdy konsument twojej klasy również próbuje zablokować na obiekcie. Poniżej tylko jeden z trzech wątków może być kontynuowany, pozostałe dwa są zakleszczone.
Aby obejść ten problem, ten facet użył Thread.TryMonitor (z limitem czasu) zamiast blokady:
https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks
źródło
SomeClass
, nadal mam ten sam impas. Ponadto, jeśli blokada w klasie głównej zostanie wykonana na innym prywatnym wystąpieniu członka Programu, nastąpi taka sama blokada. Nie jestem pewien, czy ta odpowiedź nie wprowadza w błąd i jest nieprawidłowa. Zobacz to zachowanie tutaj: dotnetfiddle.net/DMrU5hPonieważ każdy fragment kodu, który widzi instancję klasy, może również zablokować to odwołanie. Chcesz ukryć (obudować) obiekt blokujący, aby mógł odwoływać się do niego tylko kod, który musi się do niego odwoływać. Słowo kluczowe odnosi się do bieżącej instancji klasy, więc dowolna liczba rzeczy może mieć do niej odniesienie i może być używana do synchronizacji wątków.
Żeby było jasne, jest to złe, ponieważ jakiś inny fragment kodu może użyć instancji klasy do zablokowania i może uniemożliwić uzyskanie w odpowiednim czasie blokady kodu lub może spowodować inne problemy z synchronizacją wątków. Najlepszy przypadek: nic innego nie używa odniesienia do twojej klasy do zablokowania. Środkowy przypadek: coś korzysta z odwołania do twojej klasy, aby robić blokady i powoduje problemy z wydajnością. Najgorszy przypadek: coś korzysta z referencji twojej klasy do robienia blokad i powoduje naprawdę złe, bardzo subtelne, bardzo trudne do debugowania problemy.
źródło
Przykro mi, ale nie mogę zgodzić się z argumentem, że zablokowanie tego może spowodować impas. Mylisz dwie rzeczy: impas i głód.
Oto zdjęcie ilustrujące różnicę.
Wniosek
Możesz nadal bezpiecznie używać,
lock(this)
jeśli głód nici nie stanowi dla ciebie problemu. Nadal musisz pamiętać, że kiedy nić głodująca wykorzystująca nićlock(this)
kończy się w zamku, w którym twój obiekt jest zablokowany, ostatecznie skończy się wiecznym głodem;)źródło
lock(this)
- ten rodzaj kodu jest po prostu zły. Myślę, że nazwanie tego zakleszczeniem jest trochę obelżywe.Zapoznaj się z poniższym linkiem, który wyjaśnia, dlaczego blokada (to) nie jest dobrym pomysłem.
http://blogs.msdn.com/b/bclteam/archive/2004/01/20/60719.aspx
Rozwiązaniem jest więc dodanie prywatnego obiektu, na przykład lockObject do klasy i umieszczenie regionu kodu w instrukcji lock, jak pokazano poniżej:
źródło
Oto przykładowy kod, który jest prostszy do naśladowania (IMO): ( Działa w LinqPad , do następujących przestrzeni nazw: System.Net i System.Threading.Tasks)
Należy pamiętać, że lock (x) to po prostu cukier składniowy, a to, co robi, to użycie Monitor.Enter, a następnie skorzystanie z try, catch, wreszcie blok do wywołania Monitor.Exit. Zobacz: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (sekcja uwag)
Wynik
Zauważ, że Wątek nr 12 nigdy się nie kończy, ponieważ jest martwy.
źródło
DoWorkUsingThisLock
wątek nie jest konieczny do zilustrowania problemu?Możesz ustanowić regułę, która mówi, że klasa może mieć kod blokujący „to” lub dowolny obiekt, który tworzy kod w klasie. Jest to problem tylko wtedy, gdy wzór nie jest przestrzegany.
Jeśli chcesz uchronić się przed kodem, który nie będzie zgodny z tym wzorcem, zaakceptowana odpowiedź jest poprawna. Ale jeśli ten wzór jest przestrzegany, nie stanowi to problemu.
Zaletą blokady (to) jest wydajność. Co jeśli masz prosty „obiekt wartości” zawierający jedną wartość. To tylko opakowanie, które pojawia się miliony razy. Wymagając utworzenia prywatnego obiektu synchronizacji tylko do blokowania, w zasadzie podwoiłeś rozmiar obiektu i podwoiłeś liczbę przydziałów. Gdy liczy się wydajność, jest to zaleta.
Jeśli nie obchodzi Cię liczba przydziałów lub ilość pamięci, lepiej unikać blokady (z tego powodu) z powodów wskazanych w innych odpowiedziach.
źródło
Wystąpi problem, jeśli instancja będzie dostępna publicznie, ponieważ mogą istnieć inne żądania, które mogą korzystać z tej samej instancji obiektu. Lepiej jest użyć zmiennej prywatnej / statycznej.
źródło