Co sprawia, że ​​metoda jest bezpieczna dla wątków? Jakie są zasady?

156

Czy istnieją ogólne zasady / wytyczne dotyczące tego, co sprawia, że ​​metoda jest bezpieczna dla wątków? Rozumiem, że prawdopodobnie jest milion jednorazowych sytuacji, ale co w ogóle? Czy to takie proste?

  1. Jeśli metoda uzyskuje dostęp tylko do zmiennych lokalnych, jest bezpieczna wątkowo.

Czy to to? Czy dotyczy to również metod statycznych?

Jedna odpowiedź udzielona przez @Cybis brzmiała:

Zmienne lokalne nie mogą być współużytkowane między wątkami, ponieważ każdy wątek otrzymuje własny stos.

Czy tak jest również w przypadku metod statycznych?

Jeśli do metody zostanie przekazany obiekt referencyjny, czy powoduje to naruszenie bezpieczeństwa wątków? Zrobiłem trochę badań i jest wiele na temat niektórych przypadków, ale miałem nadzieję, że będę w stanie zdefiniować, używając tylko kilku reguł, wskazówek, których należy przestrzegać, aby upewnić się, że metoda jest bezpieczna dla wątków.

Więc wydaje mi się, że moje ostateczne pytanie brzmi: „Czy istnieje krótka lista reguł definiujących metodę bezpieczną dla wątków? Jeśli tak, to jakie?”

EDYCJA
Dokonano tutaj wielu dobrych punktów. Myślę, że prawdziwa odpowiedź na to pytanie brzmi: „Nie ma prostych zasad zapewniających bezpieczeństwo nici”. Chłodny. W porządku. Ale generalnie myślę, że zaakceptowana odpowiedź stanowi dobre, krótkie podsumowanie. Zawsze są wyjątki. Niech tak będzie. Moge z tym zyc.

Bob Horn
źródło
59
Nie będziesz mieć dostępu do zmiennych, do których dostęp mają również inne wątki bez zamka.
Hans Passant
4
Hanth Pathant zamienił się w igora!
Martin James,
3
Również… „Nie będziesz mieć dostępu do zmiennych, do których dostęp mają również inne wątki bez blokady” - chyba że nie ma to aż takiego znaczenia, jeśli odczytana wartość czasami nie jest najnowsza lub jest w rzeczywistości rażąco niepoprawna.
Martin James,
Oto fajny blog autorstwa Erica, który zabierze Cię w wir.
RBT

Odpowiedzi:

139

Jeśli metoda (instancja lub statyczna) odwołuje się tylko do zmiennych objętych zakresem tej metody, jest bezpieczna wątkowo, ponieważ każdy wątek ma swój własny stos:

W tym przypadku wiele wątków może wywoływać ThreadSafeMethodjednocześnie bez problemu.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

Dotyczy to również sytuacji, gdy metoda wywołuje inną metodę klasy, która odwołuje się tylko do zmiennych o zasięgu lokalnym:

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

Jeśli metoda uzyskuje dostęp do właściwości lub pól (stanu obiektu) (instancja lub statyczne), należy użyć blokad, aby upewnić się, że wartości nie są modyfikowane przez inny wątek.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

Należy pamiętać, że wszelkie parametry przekazane do metody, które nie są strukturą lub niezmienne, mogą zostać zmutowane przez inny wątek poza zakresem metody.

Aby zapewnić odpowiednią współbieżność, musisz użyć blokowania.

Aby uzyskać więcej informacji, zobacz odwołanie do instrukcji blokady C # i ReadWriterLockSlim .

lock jest najczęściej przydatny do zapewniania funkcjonalności
ReadWriterLockSlimpojedynczo , jest przydatny, jeśli potrzebujesz wielu czytelników i pojedynczych pisarzy.

Trevor Pilley
źródło
15
W trzecim przykładzie private string someValue;nie jest staticwięc każda instancja otrzyma oddzielną kopię tej zmiennej. Czy możesz więc wyjaśnić, dlaczego nie jest to bezpieczne dla wątków?
Bharadwaj
29
@Bharadwaj, jeśli istnieje jedna instancja Thingklasy, do której dostęp ma wiele wątków
Trevor Pilley
111

Jeśli metoda uzyskuje dostęp tylko do zmiennych lokalnych, jest bezpieczna wątkowo. Czy to to?

Absolutnie nie. Możesz napisać program z tylko jedną zmienną lokalną dostępną z pojedynczego wątku, który nie jest jednak bezpieczny dla wątków:

https://stackoverflow.com/a/8883117/88656

Czy dotyczy to również metod statycznych?

Absolutnie nie.

Jedna odpowiedź udzielona przez @Cybis brzmiała: „Zmienne lokalne nie mogą być współużytkowane między wątkami, ponieważ każdy wątek ma swój własny stos”.

Absolutnie nie. Cechą wyróżniającą zmienną lokalną jest to, że jest ona widoczna tylko z zakresu lokalnego , a nie to, że jest alokowana w puli tymczasowej . Dostęp do tej samej zmiennej lokalnej z dwóch różnych wątków jest całkowicie legalny i możliwy. Możesz to zrobić za pomocą metod anonimowych, lambd, bloków iteratorów lub metod asynchronicznych.

Czy tak jest również w przypadku metod statycznych?

Absolutnie nie.

Jeśli do metody zostanie przekazany obiekt referencyjny, czy powoduje to naruszenie bezpieczeństwa wątków?

Może.

Zrobiłem trochę badań i jest wiele na temat niektórych przypadków, ale miałem nadzieję, że będę w stanie zdefiniować, używając tylko kilku reguł, wskazówek, których należy przestrzegać, aby upewnić się, że metoda jest bezpieczna dla wątków.

Będziesz musiał nauczyć się żyć z rozczarowaniem. To bardzo trudny temat.

Wydaje mi się, że moje ostateczne pytanie brzmi: „Czy istnieje krótka lista reguł definiujących metodę bezpieczną dla wątków?

Nie. Jak widać z mojego wcześniejszego przykładu, metoda pusta może nie być bezpieczna dla wątków . Równie dobrze możesz zapytać „czy istnieje krótka lista reguł zapewniających poprawność metody ”. Nie, nie ma. Bezpieczeństwo nici to nic innego jak niezwykle skomplikowany rodzaj poprawności.

Co więcej, fakt, że zadajesz to pytanie, wskazuje na twoje fundamentalne nieporozumienie dotyczące bezpieczeństwa nici. Bezpieczeństwo wątków jest globalną , a nie lokalną właściwością programu. Powodem, dla którego jest to tak trudne, jest to, że musisz mieć pełną wiedzę na temat zachowania wątków całego programu , aby zapewnić jego bezpieczeństwo.

Ponownie spójrz na mój przykład: każda metoda jest trywialna . To sposób, w jaki metody współdziałają ze sobą na poziomie „globalnym”, powoduje impas w programie. Nie możesz spojrzeć na każdą metodę i odznaczyć ją jako „bezpieczną”, a następnie oczekiwać, że cały program jest bezpieczny, tak samo jak nie możesz stwierdzić, że ponieważ Twój dom jest wykonany w 100% z pustaków, to dom jest również nie pusty. Pustka w domu jest ogólną właściwością całości, a nie sumą właściwości jego części.

Eric Lippert
źródło
14
Podstawowe stwierdzenie: bezpieczeństwo wątków jest globalną, a nie lokalną właściwością programu.
Greg D
5
@BobHorn: class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } Zadzwoń do M (), a następnie wywołaj C.getter i C.setter w dwóch różnych wątkach. Zmienna lokalna może być teraz zapisywana i odczytywana w dwóch różnych wątkach, nawet jeśli jest lokalna. Ponownie: cechą definiującą zmienną lokalną jest to, że jest lokalna , a nie to, że znajduje się na stosie wątku .
Eric Lippert
3
@BobHorn: Myślę, że ma to więcej wspólnego ze zrozumieniem naszych narzędzi na takim poziomie, abyśmy mogli mówić o nich z autorytetem i wiedzą. Np. Pierwotne pytanie zdradzało brak zrozumienia, czym jest zmienna lokalna. Ten błąd jest dość powszechny, ale faktem jest, że jest to błąd i powinien zostać poprawiony. Definicja zmiennej lokalnej jest dość precyzyjna i należy ją odpowiednio traktować. :) To nie jest odpowiedź dla „ludzi”, którzy nie mają takich patologicznych przypadków. To jest wyjaśnienie, abyś mógł zrozumieć, o co właściwie prosiłeś. :)
Greg D
7
@EricLippert: Dokumentacja MSDN dla wielu klas deklaruje, że `` publiczne statyczne (udostępnione w Visual Basic) elementy tego typu są bezpieczne wątkowo. Nie ma gwarancji, że żaden członek instancji będzie bezpieczny dla wątków. ' lub czasami „Ten typ jest bezpieczny dla wątków”. (np. String), w jaki sposób można zapewnić te gwarancje, gdy bezpieczeństwo wątków jest problemem globalnym?
Mike Zboray,
3
@mikez: Dobre pytanie. Metody te albo nie mutują żadnego stanu, albo kiedy to robią, mutują stan bezpiecznie bez blokad, albo, jeśli używają blokad, robią to w sposób gwarantujący, że globalny „porządek blokad” nie jest naruszany. Łańcuchy są niezmiennymi strukturami danych, których metody niczego nie modyfikują, więc łańcuch można łatwo uczynić bezpiecznym.
Eric Lippert,
11

Nie ma sztywnej reguły.

Oto kilka reguł zapewniających bezpieczeństwo wątku kodu w .NET i dlaczego nie są to dobre reguły:

  1. Funkcja i wszystkie wywoływane przez nią funkcje muszą być czyste (bez skutków ubocznych) i używać zmiennych lokalnych. Chociaż spowoduje to, że twój kod będzie bezpieczny dla wątków, jest również bardzo mało interesujących rzeczy, które możesz zrobić z tym ograniczeniem w .NET.
  2. Każda funkcja, która operuje na wspólnym obiekcie, musi lockmieć wspólną rzecz. Wszystkie zamki należy wykonać w tej samej kolejności. Dzięki temu wątek kodu będzie bezpieczny, ale będzie niewiarygodnie wolny i równie dobrze możesz nie używać wielu wątków.
  3. ...

Nie ma reguły, która zapewnia bezpieczeństwo wątku kodu, jedyne, co możesz zrobić, to upewnić się, że kod będzie działał bez względu na to, ile razy jest aktywnie wykonywany, każdy wątek może zostać przerwany w dowolnym momencie, a każdy wątek jest w własny stan / lokalizację, i to dla każdej funkcji (statycznej lub innej), która uzyskuje dostęp do wspólnych obiektów.

EarlNameless
źródło
10
Zamki nie muszą być powolne. Zamki są niesamowicie szybkie; bezsporny zamek ma wielkość rzędu dziesięciu do stu nanosekund . Kwestionowane zamki są oczywiście arbitralnie powolne; jeśli jesteś spowolniony, ponieważ kwestionujesz blokady, przeprowadź ponowną architekturę programu, aby usunąć rywalizację .
Eric Lippert
2
Wydaje mi się, że nie udało mi się wystarczająco jasno określić # 2. Próbowałem powiedzieć, że istnieje sposób na zabezpieczenie wątków: umieszczaj blokady wokół każdego dostępu do typowych obiektów. Zakładając, że istnieje wiele wątków, spowoduje to rywalizację, a blokady spowalniają całość. Dodając zamki, nie można po prostu dodawać zamków na ślepo, trzeba je umieszczać w bardzo dobrych strategicznych miejscach.
EarlNameless