2012-01-19 19 views
5

Wiem, że nie wolno używać lock(this) lub dowolnego udostępnionego obiektu.Czy ten wątek użycia blokady jest bezpieczny?

Zastanawiam się, czy to użycie jest OK?

public class A 
{ 
    private readonly object locker = new object(); 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(locker) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    private readonly object locker = new object(); 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(locker) 
    { 
    int i = list.Count; 
    } 
    } 
} 

Czy ten wątek jest bezpieczny? W OtherClass blokujemy z prywatnym obiektem, więc jeśli class A zablokuje się z jego własną blokadą, lista może się zmienić w bloku blokowania w OtherClass?

+11

Twoja łazienka ma dwoje drzwi, każde z zamkiem. Twoje pytanie brzmi: "Przypuśćmy, że tylko blokuję pierwszy zamek, gdy jestem pod prysznicem, a mój przyjaciel Bob tylko blokuje drugi zamek, kiedy jest pod prysznicem. Czy możemy w obu przypadkach wylądować pod prysznicem w tym samym czasie?" Oczywiście tak! Jeśli Ty i Bob chcecie uniknąć wspólnego brania prysznica, musicie zgodzić się na oba zastosowania * tego samego zamka *. Nie można uzyskać dostępu do obiektu w taki sposób jak wątki. –

Odpowiedz

10

Nie, to nie jest bezpieczne dla wątków. Dodawanie i liczenie może być wykonywane w tym samym czasie. Masz dwa różne obiekty blokujące.

Zawsze zablokować swój własny obiekt blokady podczas przechodzenia listy:

public void MethodeB() 
    { 
    lock(locker) 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

W rzeczywistości jest bezpieczny dla wątków, ale tylko jako szczegół implementacji "Count", który nie jest obiecany w wątku, a nie w sposób, który rozszerzyłby się na bardziej realistyczny kod. –

2

Nie, to nie jest bezpieczne dla wątków.

Twoje 2 metody blokują 2 różne obiekty, nie będą się blokować.

Ponieważ CallToMethodInOtherClass() tylko pobiera wartość Count nic nie pójdzie strasznie źle. Jednak wokół niego jest bezużyteczny i mylący.

Jeśli metoda wprowadziłaby zmiany na liście, problem byłby nieprzyjemny. Aby go rozwiązać, zmień MetodeB:

public void MethodeB() 
    { 
    lock(locker) // same instance as MethodA is using 
    { 
     CallToMethodInOtherClass(myList); 
    } 
    } 
+0

to, co myślałem. więc możesz mi powiedzieć, jak to działa? – Maya

2

Nie, muszą zablokować ten sam obiekt. Za pomocą twojego kodu blokują się na różnych i każde połączenie może być wykonane równocześnie.

Aby zapewnić bezpieczny wątek kodu, umieść blokadę w MethodeB lub użyj samej listy jako obiektu blokady.

+0

@ Felix Myślałem, że to źle, aby zablokować za pomocą współdzielonego obiektu, więc zablokuj z samą listą, że jest zła? – Maya

+0

@Maya To prawda, to źle. Ale jest to łatwy sposób na rozwiązanie opisanego problemu. Ale w każdym razie właściwym sposobem byłoby stworzenie listy bezpiecznej dla wątków lub kontenera, który zawiera listę i dostęp do listy tylko przez kontener. –

3

Nie, to nie jest bezpieczny wątku. A.MethodeA i OtherClass.CallToMethodInOtherClass blokują różne obiekty, więc nie wykluczają się wzajemnie. Jeśli chcesz zabezpieczyć dostęp do listy, nie przekazuj jej do kodu zewnętrznego, zachowaj ją jako prywatną.

0

Prawdopodobnie najłatwiejszym sposobem rade

public class A 
{ 
    private List<int> myList; 
    public A() 
    { 
    myList = new List<int>() 
    } 

    private void MethodeA() 
    { 
    lock(myList) 
    { 
     myList.Add(10); 
    } 
    } 

    public void MethodeB() 
    { 
    CallToMethodInOtherClass(myList); 
    } 
} 

public class OtherClass 
{ 
    public CallToMethodInOtherClass(List<int> list) 
    { 
    lock(list) 
    { 
    int i = list.Count; 
    } 
    } 
} 
+0

ale myślałem, że jego wront do zablokowania z shraed obiektu – Maya

1

Ass wszystkie odpowiedzi powiedzenia są to różne obiekty lock.

prostym sposobem jest mieć statyczny obiekt blokady f.ex:

publc class A 
{ 
    public static readonly object lockObj = new object(); 
} 

iw obu klas użyć blokady jak:

lock(A.lockObj) 
{ 
} 
3

No to nie jest bezpieczeństwo wątków. Aby zapewnić bezpieczeństwo wątku, można użyć blokady na obiektach static, ponieważ są one współużytkowane przez wątki, co może powodować zakleszczenia w kodzie, ale można je obsługiwać, zachowując odpowiednią kolejność blokowania. Istnieje koszt związany z wydajnością związany z lock, więc używaj go mądrze.

Nadzieja to pomaga

+0

w jaki sposób to mi pomóc? – Maya

+0

dziękuję, teraz rozumiem – Maya

+0

Obiekty statyczne są współużytkowane między innymi obiektami, więc jeśli umieścisz w tym celu blokadę, zostanie ona pobrana tylko przez jeden obiekt naraz. Więcej informacji na temat [statyczny w MSDN] (http://msdn.microsoft.com/en-us/library/79b3xss3 (v = vs.80) .aspx) lub [blokada MSDN] (http://msdn.microsoft .com/en-us/library/c5kehkcz (v = vs.80) .aspx) (_Najlepszą praktyką jest zdefiniowanie prywatnego obiektu do zablokowania lub prywatnej statycznej zmiennej obiektowej w celu ochrony danych wspólnych dla wszystkich instancji._ MSDN) –

0

Wiele odpowiedzi wspomniałem przy użyciu statycznej readonly blokadę.

Jednak naprawdę powinieneś unikać tej blokady statycznej.Łatwo byłoby stworzyć zakleszczenie, w którym wiele wątków używa blokady statycznej.

To, co można zamiast tego wykorzystać, to jedna z 4 jednoczesnych kolekcji .NET, które zapewniają pewną synchronizację wątków w twoim imieniu, dzięki czemu nie musisz używać blokady.

Spójrz na przestrzeń nazw System.collections.Concurrent. W tym przykładzie można użyć klasy ConcurrentBag<T>.

1

To faktycznie jest thread-safe (wyłącznie jako przedmiot szczegółów wdrażania na Count), ale:

  1. wątku bezpieczny fragmentów kodu NIE wątek bezpieczne stosowanie makijażu. Można łączyć operacje z różnymi wątkami w operacje, które nie są wątkowane. Rzeczywiście, wiele nie-wątkowych bezpiecznych kodów można podzielić na mniejsze części, z których wszystkie są same bezpieczne dla wątków.

  2. Nie jest bezpieczny dla wątków z powodów, na które liczył, co oznacza, że ​​rozszerzenie go dalej nie byłoby bezpieczne dla wątków.

Kod ten byłby bezpieczny wątku:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    //note we've no locks! 
    int i = list.Count; 
    //do something with i but don't touch list again. 
} 

nazywają go z każdej listy, a będzie to dać i wartość w oparciu o stan tej listy, niezależnie od tego, jakie są inne wątki aż do. To nie uszkodzi list. Nie spowoduje to niepoprawnej wartości i.

Więc gdy kod ten jest również bezpieczny wątku:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    Console.WriteLine(list[93]); // obviously only works if there's at least 94 items 
          // but that's nothing to do with thread-safety 
} 

Kod ten nie byłby bezpieczny wątku:

public void CallToMethodInOtherClass(List<int> list) 
{ 
    lock(locker)//same as in the question, different locker to that used elsewhere. 
    { 
    int i = list.Count; 
    if(i > 93) 
     Console.WriteLine(list[93]); 
    } 
} 

Zanim przejdziemy dalej, dwa bity opisałem jak thread- bezpieczne nie są podane w specyfikacji dla listy. Konserwatywne kodowanie zakłada, że ​​nie są bezpieczne dla wątków, a nie zależą od szczegółów implementacji, ale zamierzam polegać na szczegółach implementacji, ponieważ ma to wpływ na sposób użycia zamków w istotny sposób:

Ponieważ istnieje kod działający pod numerem list, który nie uzyskuje blokady locker po pierwsze, ten kod nie jest chroniony przed równoczesnym działaniem z CallToMethodInOtherClass. Teraz, podczas gdy list.Count jest bezpieczny dla wątków, a list[93] jest bezpieczny na bieżnik, * połączenie dwóch elementów, od których zależymy od pierwszego, aby zapewnić, że drugi działa nie jest bezpieczny dla wątków. Ponieważ kod znajdujący się poza blokadą może wpływać na kod list, kod może zadzwonić pod numer Remove lub Clear między Count, co zapewni nam, że zadziała list[93], a list[93].

Teraz, jeśli wiemy, że list jest tylko dodany do tego, to w porządku, nawet jeśli zmiana rozmiaru dzieje się jednocześnie, otrzymamy wartość list[93] w obie strony. Jeśli coś pisze do list[93] i jest to typ, który .NET zapisuje do postaci atomowej (i jest to jeden z takich typów), to skończy się na starym lub nowym, tak jakbyśmy zablokowali poprawnie "Daj stary lub nowy w zależności od tego, który wątek trafi pierwszy.Ponownie, jest to szczegół implementacji, a nie określona obietnica., Podkreślam to tylko po to, aby wskazać, w jaki sposób podane bezpieczeństwo wątków nadal powoduje, że kod nie jest wątkowo bezpieczny.

Przesuwając to w kierunku prawdziwego kodu. Nie powinniśmy zakładać, że list.Count i list[93] jest bezpieczny dla wątków, ponieważ nie obiecano nam, że będą, a to może się zmienić, ale nawet gdybyśmy mieli tę obietnicę, obie te obietnice nie dałyby obietnicy, że będą bezpieczne dla wątków razem.

Ważne jest, aby używać tego samego zamka do ochrony bloków kodu, które mogą ze sobą ingerować. Stąd też, należy rozważyć wariant poniżej, który jest gwarancją threadsafe:

public class ThreadSafeList 
{ 
    private readonly object locker = new object(); 
    private List<int> myList = new List<int>(); 

    public void Add(int item) 
    { 
    lock(locker) 
     myList.Add(item); 
    } 
    public void Clear() 
    { 
    lock(locker) 
     myList.Clear(); 
    } 
    public int Count 
    { 
    lock(locker) 
     return myList.Count; 
    } 
    public int Item(int index) 
    { 
    lock(locker) 
     return myList[index]; 
    } 
} 

Ta klasa jest gwarancją threadsafe wszystko robi. Bez zależności od szczegółów implementacji, nie ma tu żadnej metody, która uszkodzi stan lub da niepoprawne wyniki z powodu tego, co robi inny wątek z tym samym wystąpieniem. Poniższy kod nadal nie działa jednak:

// (l is a ThreadSafeList visible to multiple threads. 
if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Mamy zagwarantowane nitki bezpieczeństwo każdego wywołania 100%, ale nie mamy zagwarantowane połączenie, a my nie możemygwarancji kombinacja.

Możemy zrobić dwie rzeczy. Możemy dodać metodę dla kombinacji. Coś jak poniżej byłyby wspólne dla wielu klas zaprojektowanych specjalnie dla wielowątkowych użytku:

public bool TryGetItem(int index, out int value) 
{ 
    lock(locker) 
    { 
    if(l.Count > index) 
    { 
     value = l[index]; 
     return true; 
    } 
    value = 0; 
    return false; 
    } 
} 

To sprawia, że ​​test licznika i część pobierania pozycja pojedynczej operacji, która jest gwarantowana być bezpieczny wątku.

Alternatywnie, a najczęściej co musimy zrobić, mamy blokadę stało w miejscu, gdzie operacje są zgrupowane:

lock(lockerOnL)//used by every other piece of code operating on l 
    if(l.Count > 0) 
    Console.WriteLine(l[0]); 

Oczywiście, to sprawia, że ​​zamki w ciągu ThreadSafeList zbędny i tylko strata wysiłku, przestrzeni i czasu. Jest to główny powód, dla którego większość klas nie zapewnia bezpieczeństwa wątków na swoich członkach instancji - ponieważ nie możesz znacząco zabezpieczyć grup połączeń od członków z klasy, to strata czasu próbuje, chyba że obiecuje bezpieczeństwo wątków są bardzo dobrze określone i przydatne na własną rękę.

Aby wrócić do kodu w swoim pytaniu:

Blokada w CallToMethodInOtherClass powinny być usunięte, chyba OtherClass ma swój własny powód blokowania wewnętrznie. Nie może wnieść znaczącej obietnicy, że nie zostanie połączony w sposób niechroniony wątkami, a dodanie kolejnych blokad do programu tylko zwiększy złożoność analizy, aby upewnić się, że nie ma żadnych zakleszczeń.

Wezwanie do CallToMethodInOtherClass powinny być chronione przez tę samą blokadą innych operacji w tej klasie:

public void MethodeB() 
{ 
    lock(locker) 
    CallToMethodInOtherClass(myList); 
} 

Następnie tak długo jak CallToMethodInOtherClass nie przechowuje myList gdzieś można zauważyć przez innych wątków później, nie ma znaczenia, że ​​CallToMethodInOtherClass nie jest bezpieczny dla wątków, ponieważ jedyny kod, który może uzyskać dostęp do myList, przynosi własną gwarancję, że nie będzie go wywoływać jednocześnie z innymi operacjami na myList.

Te dwie ważne rzeczy:

  1. Kiedy coś jest opisany jako „thread-safe”, wiemy tylko to, co jest obiecujące o tym, jak istnieją różne rodzaje obietnicą, że wchodzą w „wątku bezpieczny "i samo w sobie oznacza to po prostu" nie umieszczę tego przedmiotu w stanie bezsensownym ", który - choć jest ważnym elementem konstrukcyjnym - nie jest sam w sobie.

  2. Blokada na grup operacji, z tym samym zamkiem dla każdej grupy, które będą wpływać na te same dane, i strzec dostępu do obiektów tak, że nie może być ewentualnie inny wątek nie grać w piłkę z tym.

* Jest to bardzo ograniczona definicja nici bezpiecznych. Wywołanie list[93] na List<T> gdzie jest typem, który zostanie zapisany i odczytany atomowo i nie wiemy, czy faktycznie ma co najmniej 94 elementy jest równie bezpieczny, czy działają na nim inne wątki. Oczywiście, w obu przypadkach nie jest to możliwe, aby większość ludzi uznała to za "bezpieczne", ale gwarancja, którą mamy z wieloma wątkami, pozostaje taka sama jak w przypadku jednego. Chodzi o to, że uzyskamy silniejszą gwarancję, sprawdzając Count w jednym wątku, ale nie w sytuacji wielowątkowej, która prowadzi mnie do opisania tego jako nie wątku bezpiecznego; podczas gdy ta kombinacja nadal nie spowoduje zepsucia państwa, może doprowadzić do wyjątku, który sami zapewnimy, że nie może się zdarzyć.

+0

Zamiast tego użyj ConcurrentBag lub ConcurrentDictionary, wtedy nie będziesz musiał się martwić o blokowanie. –

+0

@ PålThingbø Nieprawda w najmniejszym stopniu. Najpierw te kolekcje, a nawet mój własny słownik i zestaw słowników (https://bitbucket.org/JonHanna/ariadne) i niektóre inne tego typu kolekcje, są bezpieczne dla wątków dla każdego pojedynczego działania na nich, grup działań na nich niekoniecznie są wątkowo bezpieczne z powodów które podaję powyżej (pomyśl o tym, 'int' jest wątkowo bezpieczny, ale to nie powoduje, że' ++ x' jest bezpieczny dla wątków, ponieważ trzy wątkowe akcje, które nie są wątkami bezpiecznymi jako jednostka). Bardzo rzadko wskazuje się na to, że ktoś chce użyć torby lub słownika, kiedy chce listy; ... –

+0

@ Semantyka PålThingbø tych klas jest zupełnie inna. Mam nadzieję, że wkrótce udostępnimy klasę listy bezpiecznych wątków, która zapewnia znacznie lepsze zachowanie współbieżne niż w powyższej odpowiedzi, chociaż nadal będzie ograniczona (brak 'RemoveAt' lub' Insert'; mogę utworzyć inną klasę, która je posiada zbyt późno, ale wykonanie ich w sposób bezpieczny dla wątków bez blokowania jest o wiele bardziej uciążliwe niż reszta) i nadal nie spowoduje, że kod użyje go w sposób magiczny, jako całości, tak samo jak "ConcurrentDictionary" z powodów Daję powyżej. –

Powiązane problemy