Co oznacza Threadafe?

125

Niedawno próbowałem uzyskać dostęp do pola tekstowego z wątku (innego niż wątek interfejsu użytkownika) i został zgłoszony wyjątek. Mówił coś o tym, że „kod nie jest bezpieczny dla wątków”, więc napisałem delegata (pomógł przykład z MSDN) i wywołałem go.

Ale mimo to nie bardzo rozumiałem, dlaczego cały dodatkowy kod był potrzebny.

Aktualizacja: Czy jeśli sprawdzę, napotkam jakieś poważne problemy

Controls.CheckForIllegalCrossThread..blah =true
Vivek Bernard
źródło
5
Zazwyczaj „bezpieczny wątek” oznacza cokolwiek osoba używająca tego terminu myśli, że oznacza, przynajmniej dla tej osoby. W związku z tym nie jest to zbyt przydatna konstrukcja językowa - musisz być dużo, dużo bardziej szczegółowy, gdy mówisz o zachowaniu kodu z wątkami.
@dave Niestety próbowałem poszukiwania, ale zrezygnował ... dzięki tak ..
Vivek Bernard
1
kod, który się nie pojawiaRace-Condition
Muhammad Babar

Odpowiedzi:

121

Eric Lippert zamieścił fajny wpis na blogu zatytułowany Co to jest to, co nazywasz „bezpiecznym wątkiem”? o definicji bezpieczeństwa nici, znalezionej w Wikipedii.

3 ważne rzeczy wyodrębnione z linków:

„Fragment kodu jest bezpieczny dla wątków, jeśli działa poprawnie podczas jednoczesnego wykonywania przez wiele wątków”.

„W szczególności musi spełniać potrzebę dostępu wielu wątków do tych samych udostępnionych danych…”

„… I potrzeba, aby udostępniony fragment danych był dostępny tylko dla jednego wątku w danym momencie”.

Zdecydowanie warte przeczytania!

Gregory Pakosz
źródło
25
Unikaj odpowiedzi zawierających tylko linki, ponieważ w przyszłości może to stać się złe.
akhil_mittal
1
zaktualizowany link: docs.microsoft.com/en-nz/archive/blogs/ericlippert/…
Ryan Buddicom
106

Mówiąc najprościej, ochrona wątków oznacza, że ​​dostęp z wielu wątków jest bezpieczny. Kiedy używasz wielu wątków w programie i każdy z nich próbuje uzyskać dostęp do wspólnej struktury danych lub lokalizacji w pamięci, może się zdarzyć kilka złych rzeczy. Więc dodajesz dodatkowy kod, aby zapobiec tym złym rzeczom. Na przykład, jeśli dwie osoby pisały ten sam dokument w tym samym czasie, druga zapisywana osoba nadpisze pracę pierwszej osoby. Aby zapewnić bezpieczeństwo wątku, musisz zmusić osobę 2 do czekania, aż osoba 1 wykona swoje zadanie, zanim zezwolisz osobie 2 na edycję dokumentu.

Vincent Ramdhanie
źródło
11
Nazywa się to synchronizacją. Dobrze?
JavaTechnical
3
Tak. Zmuszenie różnych wątków do oczekiwania na dostęp do współdzielonego zasobu można osiągnąć poprzez synchronizację.
Vincent Ramdhanie,
Z zaakceptowanej odpowiedzi Gregory mówi: „Fragment kodu jest bezpieczny dla wątków, jeśli działa poprawnie podczas jednoczesnego wykonywania przez wiele wątków”. podczas gdy mówisz „Aby to było bezpieczne, musisz zmusić osobę 1 do czekania”; czy nie mówi, że jednoczesne jest dopuszczalne, kiedy mówisz, że nie? Czy możesz wyjaśnić?
Kochanie,
To jest to samo. Po prostu proponuję prosty mechanizm jako przykład tego, co sprawia, że ​​kod jest bezpieczny dla wątków. niezależnie od zastosowanego mechanizmu, mimo że wiele wątków wykonujących ten sam kod nie powinny ze sobą kolidować.
Vincent Ramdhanie
Czy zatem dotyczy to tylko kodu wykorzystującego zmienne globalne i statyczne? Korzystając z przykładu osób edytujących dokumenty, przypuszczam, że nie ma sensu uniemożliwiać osobie 2 uruchamiania kodu do pisania dokumentu na innym dokumencie.
Aaron Franke,
18

Wikipedia zawiera artykuł na temat bezpieczeństwa wątków.

Ta strona z definicjami (musisz pominąć reklamę - przepraszam) definiuje ją w ten sposób:

W programowaniu komputerowym wątkowo bezpieczne opisuje część programu lub procedurę, którą można wywołać z wielu wątków programowania bez niepożądanej interakcji między wątkami.

Wątek to ścieżka wykonywania programu. Program z pojedynczym wątkiem będzie miał tylko jeden wątek, więc ten problem nie występuje. Praktycznie wszystkie programy GUI mają wiele ścieżek wykonywania, a tym samym wątków - są co najmniej dwa, jeden do przetwarzania wyświetlania GUI i obsługi danych wejściowych użytkownika, a co najmniej jeden do faktycznego wykonywania operacji programu.

Dzieje się tak, aby interfejs użytkownika nadal reagował, gdy program działa, odciążając dowolny długo działający proces do wszystkich wątków niezwiązanych z interfejsem użytkownika. Te wątki mogą być tworzone raz i istnieć przez cały czas trwania programu lub po prostu zostać utworzone, gdy są potrzebne, i zniszczone po zakończeniu.

Ponieważ te wątki często będą musiały wykonywać typowe czynności - operacje wejścia / wyjścia dysku, wyświetlanie wyników na ekranie itp. - te części kodu będą musiały być napisane w taki sposób, aby mogły obsługiwać wywołania z wielu wątków, często w o tym samym czasie. Będzie to obejmować takie rzeczy, jak:

  • Praca na kopiach danych
  • Dodawanie blokad wokół krytycznego kodu
ChrisF
źródło
8

Po prostu bezpieczeństwo wątków oznacza, że ​​metoda lub instancja klasy może być używana przez wiele wątków jednocześnie bez żadnych problemów.

Rozważ następującą metodę:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

Teraz wątek A i wątek B chciałyby wykonać AddOne (). ale A zaczyna pierwszy i czyta wartość myInt (0) do tmp. Teraz z jakiegoś powodu planista decyduje się zatrzymać wątek A i odroczyć wykonanie do wątku B. Wątek B odczytuje teraz również wartość myInt (nadal 0) do swojej własnej zmiennej tmp. Wątek B kończy całą metodę, więc na końcu myInt = 1. Zwracane jest 1. Teraz kolej na wątek A. Wątek A jest kontynuowany. I dodaje 1 do tmp (tmp wynosi 0 dla wątku A). A następnie zapisuje tę wartość w myInt. myInt to znowu 1.

Więc w tym przypadku metoda AddOne została wywołana dwa razy, ale ponieważ metoda nie została zaimplementowana w sposób bezpieczny dla wątków, wartość myInt nie wynosi 2, jak oczekiwano, ale 1, ponieważ drugi wątek odczytuje zmienną myInt przed zakończeniem pierwszego wątku aktualizowanie.

Tworzenie metod bezpiecznych dla wątków jest bardzo trudne w nietrywialnych przypadkach. Istnieje kilka technik. W Javie możesz oznaczyć metodę jako zsynchronizowaną, co oznacza, że ​​tylko jeden wątek może wykonywać tę metodę w danym czasie. Pozostałe wątki czekają w kolejce. To sprawia, że ​​wątek metody jest bezpieczny, ale jeśli jest dużo pracy do wykonania w metodzie, to marnuje to dużo miejsca. Inną techniką jest „oznaczenie tylko niewielkiej części metody jako zsynchronizowanej”tworząc blokadę lub semafor i blokując tę ​​małą część (zwykle nazywaną sekcją krytyczną). Istnieją nawet metody, które są zaimplementowane jako bezpieczne dla wątków bez blokowania, co oznacza, że ​​są zbudowane w taki sposób, że wiele wątków może biegać przez nie w tym samym czasie, nigdy nie powodując problemów, może tak być w przypadku, gdy metoda wykonuje jedno wywołanie atomowe. Wywołania atomowe to wywołania, których nie można przerwać i mogą być wykonywane tylko przez jeden wątek naraz.

Sujith PS
źródło
if metoda AddOne została wywołana dwa razy
Sujith PS
7

W prawdziwym świecie przykładem dla laika jest

Załóżmy, że masz konto bankowe w bankowości internetowej i mobilnej, a Twoje konto ma tylko 10 USD. Dokonałeś przelewu środków na inne konto za pomocą bankowości mobilnej, aw międzyczasie robiłeś zakupy online, korzystając z tego samego konta bankowego. Jeśli to konto bankowe nie jest bezpieczne dla wątków, wówczas bank pozwala na wykonanie dwóch transakcji jednocześnie, a następnie bank zbankrutuje.

Threadsafe oznacza, że ​​stan obiektu nie zmienia się, jeśli jednocześnie wiele wątków próbuje uzyskać dostęp do obiektu.

Yasir Shabbir Choudhary
źródło
5

Więcej wyjaśnień można znaleźć w książce „Java Concurrency in Practice”:

Klasa jest bezpieczna dla wątków, jeśli zachowuje się poprawnie podczas uzyskiwania dostępu z wielu wątków, niezależnie od planowania lub przeplatania wykonywania tych wątków przez środowisko wykonawcze i bez dodatkowej synchronizacji lub innej koordynacji ze strony kodu wywołującego.

Jacky
źródło
4

Moduł jest bezpieczny dla wątków, jeśli gwarantuje, że może zachować niezmienniki w obliczu użycia wielowątkowego i współbieżnego.

W tym przypadku moduł może być strukturą danych, klasą, obiektem, metodą / procedurą lub funkcją. Zasadniczo fragment kodu i powiązane dane.

Gwarancja może być potencjalnie ograniczona do określonych środowisk, takich jak określona architektura procesora, ale musi obowiązywać dla tych środowisk. Jeśli nie ma wyraźnego rozgraniczenia środowisk, zwykle zakłada się, że dla wszystkich środowisk obowiązuje możliwość skompilowania i wykonania kodu.

Moduły niebezpieczne dla wątków mogą działać poprawnie w wielowątkowym i współbieżnym użyciu, ale często jest to bardziej kwestią szczęścia i zbiegów okoliczności niż starannego projektowania. Nawet jeśli jakiś moduł nie zepsuje się dla ciebie, może się zepsuć po przeniesieniu do innego środowiska.

Błędy wielowątkowe są często trudne do debugowania. Niektóre z nich zdarzają się sporadycznie, inne przejawiają się agresywnie - to również może być specyficzne dla środowiska. Mogą objawiać się jako subtelnie błędne wyniki lub impas. Mogą zepsuć struktury danych w nieprzewidywalny sposób i spowodować pojawienie się innych pozornie niemożliwych błędów w innych odległych częściach kodu. Może to być bardzo specyficzne dla aplikacji, więc trudno jest podać ogólny opis.

Chris Vest
źródło
3

Bezpieczeństwo nici : program bezpieczny wątkowo chroni dane przed błędami spójności pamięci. W programie wielowątkowym program bezpieczny dla wątków nie powoduje żadnych skutków ubocznych z wieloma operacjami odczytu / zapisu z wielu wątków na tych samych obiektach. Różne wątki mogą udostępniać i modyfikować dane obiektu bez błędów spójności.

Bezpieczeństwo wątków można osiągnąć, korzystając z zaawansowanego interfejsu API współbieżności. Ta strona dokumentacji zawiera dobre konstrukcje programistyczne do osiągnięcia bezpieczeństwa wątków.

Obiekty blokujące obsługują idiomy blokowania, które upraszczają wiele współbieżnych aplikacji.

Wykonawcy definiują interfejs API wysokiego poziomu do uruchamiania wątków i zarządzania nimi. Implementacje modułu wykonawczego dostarczane przez java.util.concurrent zapewniają zarządzanie pulą wątków odpowiednie dla aplikacji na dużą skalę.

Kolekcje współbieżne ułatwiają zarządzanie dużymi zbiorami danych i mogą znacznie zmniejszyć potrzebę synchronizacji.

Zmienne atomowe mają funkcje, które minimalizują synchronizację i pomagają uniknąć błędów spójności pamięci.

ThreadLocalRandom (w JDK 7) zapewnia wydajne generowanie liczb pseudolosowych z wielu wątków.

Zobacz pakiety java.util.concurrent i java.util.concurrent.atomic dla innych konstrukcji programistycznych.

Ravindra babu
źródło
1

Oczywiście pracujesz w środowisku WinForms. Kontrolki WinForms wykazują powinowactwo wątków, co oznacza, że ​​wątek, w którym zostały utworzone, jest jedynym wątkiem, którego można użyć do uzyskania do nich dostępu i aktualizacji. Dlatego w witrynie MSDN i innych miejscach znajdziesz przykłady pokazujące, jak skierować wywołanie z powrotem do głównego wątku.

Normalną praktyką WinForm jest posiadanie jednego wątku, który jest przeznaczony do pracy z interfejsem użytkownika.

David M.
źródło
1

Uważam, że koncepcja http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 jest tym, co zwykle uważam za niebezpieczne wątkowanie, które ma miejsce, gdy metoda ma i polega na efekcie ubocznym, takim jak zmienna globalna.

Na przykład widziałem kod, który sformatował liczby zmiennoprzecinkowe w łańcuchy, jeśli dwa z nich są uruchomione w różnych wątkach, globalna wartość decimalSeparator może zostać trwale zmieniona na „.”

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp
Aaron Robson
źródło
-2

Aby zrozumieć bezpieczeństwo wątków, przeczytaj poniższe sekcje :

4.3.1. Przykład: śledzenie pojazdu przy użyciu delegacji

Jako bardziej istotny przykład delegowania, skonstruujmy wersję modułu śledzącego pojazdy, która deleguje do klasy bezpiecznej wątkowo. Możemy zapisać lokalizacje w mapę, więc zacząć od wdrożenia Mapa thread-safe, ConcurrentHashMap. Lokalizację przechowujemy również przy użyciu niezmiennej klasy Point zamiast MutablePoint, jak pokazano na listingu 4.6.

Listing 4.6. Immutable Point Klasa używana przez DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Pointjest bezpieczny dla wątków, ponieważ jest niezmienny. Niezmienne wartości można swobodnie udostępniać i publikować, dzięki czemu nie musimy już kopiować lokalizacji podczas ich zwracania.

DelegatingVehicleTrackerna listingu 4.7 nie używa żadnej jawnej synchronizacji; cały dostęp do stanu jest zarządzany przez ConcurrentHashMap, a wszystkie klucze i wartości mapy są niezmienne.

Listing 4.7. Delegowanie bezpieczeństwa wątków do ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

Gdybyśmy użyli oryginalnej MutablePointklasy zamiast Point, zerwalibyśmy hermetyzację, zezwalając na getLocationspublikację odwołania do mutowalnego stanu, który nie jest bezpieczny dla wątków. Zwróć uwagę, że nieznacznie zmieniliśmy zachowanie klasy śledzenia pojazdów; podczas gdy wersja monitorująca zwróciła migawkę lokalizacji, wersja delegująca zwraca niemodyfikowalny, ale „na żywo” widok lokalizacji pojazdów. Oznacza to, że jeśli wątek A wywoła, getLocationsa wątek B później zmodyfikuje lokalizację niektórych punktów, te zmiany zostaną odzwierciedlone w mapie zwróconej do wątku A.

4.3.2. Niezależne zmienne stanu

Możemy również delegować bezpieczeństwo wątków do więcej niż jednej podstawowej zmiennej stanu, o ile te podstawowe zmienne stanu są niezależne, co oznacza, że ​​klasa złożona nie narzuca żadnych niezmienników obejmujących wiele zmiennych stanu.

VisualComponentna listingu 4.9 jest komponentem graficznym, który umożliwia klientom rejestrowanie nasłuchiwania zdarzeń myszy i naciśnięć klawiszy. Utrzymuje listę zarejestrowanych detektorów każdego typu, dzięki czemu w przypadku wystąpienia zdarzenia można wywołać odpowiednie nasłuchiwanie. Nie ma jednak związku między zbiorem słuchaczy myszy i słuchaczy kluczy; te dwa są niezależne i dlatego VisualComponentmogą delegować swoje zobowiązania dotyczące bezpieczeństwa wątków do dwóch bazowych list bezpiecznych wątków.

Listing 4.9. Delegowanie bezpieczeństwa wątków do wielu bazowych zmiennych stanu.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentużywa a CopyOnWriteArrayListdo przechowywania każdej listy słuchaczy; jest to bezpieczna dla wątków implementacja list, szczególnie odpowiednia do zarządzania listami słuchaczy (zobacz Rozdział 5.2.3). Każda lista jest bezpieczna dla wątków, a ponieważ nie ma żadnych ograniczeń łączących stan jednego ze stanem drugiego, VisualComponentmoże delegować obowiązki związane z bezpieczeństwem wątków na elementy bazowe mouseListenersi keyListenersobiekty.

4.3.3. Gdy delegacja się nie powiedzie

Większość klas złożonych nie jest tak prosta, jak VisualComponent: mają niezmienniki, które wiążą swoje składowe zmienne stanu. NumberRangena listingu 4.10 używa dwóch AtomicIntegersdo zarządzania swoim stanem, ale nakłada dodatkowe ograniczenie - pierwsza liczba jest mniejsza lub równa drugiej.

Listing 4.10. Klasa zakresu liczb, która nie chroni wystarczająco swoich niezmienników. Nie rób tego.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangenie jest bezpieczny dla wątków ; nie zachowuje niezmiennika, który ogranicza dolną i górną część. setLowerI setUppermetody próbować respektować tę niezmiennik, ale czy tak źle. Obie setLoweri setUppersą sekwencjami check-then-act, ale nie używają wystarczającego blokowania, aby uczynić je atomowymi. Jeśli zakres numerów obejmuje (0, 10), a jeden wątek wywołuje, setLower(5)podczas gdy inny wątek wywołuje setUpper(4), z pewnym niefortunnym czasem, oba przejdą sprawdzanie w ustawieniach i obie modyfikacje zostaną zastosowane. W rezultacie zakres zawiera teraz (5, 4) - stan nieprawidłowy . Tak więc, podczas gdy podstawowe AtomicIntegers są bezpieczne dla wątków, klasa złożona nie jest . Ponieważ bazowe zmienne stanu loweriuppernie są niezależne, NumberRangenie mogą po prostu delegować bezpieczeństwa wątków do swoich zmiennych stanu bezpiecznych dla wątków.

NumberRangemożna by uczynić bezpiecznymi dla wątków poprzez zastosowanie blokowania w celu zachowania jego niezmienności, takich jak osłona dolnej i górnej części za pomocą wspólnego zamka. Musi również unikać publikowania dolnej i górnej części, aby klienci nie mogli podważać swoich niezmienników.

Jeśli klasa ma akcje złożone, NumberRangeto samo delegowanie ponownie nie jest odpowiednim podejściem do bezpieczeństwa wątków. W takich przypadkach klasa musi zapewnić własne blokowanie, aby zapewnić, że akcje złożone są niepodzielne, chyba że cała akcja złożona może być również delegowana do bazowych zmiennych stanu.

Jeśli klasa składa się z wielu niezależnych zmiennych stanu bezpiecznych dla wątków i nie ma operacji, które mają jakiekolwiek nieprawidłowe przejścia stanu, może delegować bezpieczeństwo wątków do bazowych zmiennych stanu.

nadmierna
źródło