To faktycznie jest thread-safe (wyłącznie jako przedmiot szczegółów wdrażania na Count
), ale:
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.
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:
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.
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ć.
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. –