Co oznacza termin „bezpieczny dla wątków”?

367

Czy to oznacza, że ​​dwa wątki nie mogą jednocześnie zmienić podstawowych danych? Czy też oznacza to, że dany segment kodu będzie działał z przewidywalnymi wynikami, gdy wiele wątków wykonuje ten segment kodu?

Varun Mahajan
źródło
8
Właśnie zobaczyłem tutaj interesującą dyskusję na ten temat: blogs.msdn.com/ericlippert/archive/2009/10/19/…
Sebastian,

Odpowiedzi:

256

Z Wikipedii:

Bezpieczeństwo wątków jest koncepcją programowania komputerowego stosowaną w kontekście programów wielowątkowych. 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 zaspokoić potrzebę dostępu do tych samych współużytkowanych danych przez wiele wątków oraz potrzebę dostępu do udostępnionego fragmentu danych tylko przez jeden wątek w danym momencie.

Istnieje kilka sposobów na osiągnięcie bezpieczeństwa wątków:

Ponowne wejście:

Pisanie kodu w taki sposób, że może być częściowo wykonane przez jedno zadanie, ponownie wprowadzone przez inne zadanie, a następnie wznowione od pierwotnego zadania. Wymaga to zapisania informacji o stanie w zmiennych lokalnych dla każdego zadania, zwykle na stosie, zamiast w zmiennych statycznych lub globalnych.

Wzajemne wykluczenie:

Dostęp do udostępnionych danych jest szeregowany przy użyciu mechanizmów, które zapewniają, że tylko jeden wątek odczytuje lub zapisuje udostępnione dane w dowolnym momencie. Należy zachować szczególną ostrożność, jeśli fragment kodu uzyskuje dostęp do wielu wspólnych danych - problemy obejmują warunki wyścigu, impasy, blokady na żywo, głód i różne inne choroby wyliczone w wielu podręcznikach systemów operacyjnych.

Magazyn lokalny wątków:

Zmienne są zlokalizowane, dzięki czemu każdy wątek ma własną kopię prywatną. Zmienne te zachowują swoje wartości poza podprogramem i innymi granicami kodu i są bezpieczne dla wątków, ponieważ są lokalne dla każdego wątku, nawet jeśli kod, który uzyskuje do nich dostęp, może być ponownie wysłany.

Operacje atomowe:

Dostęp do współdzielonych danych można uzyskać za pomocą operacji atomowych, których nie mogą przerwać inne wątki. Zwykle wymaga to użycia specjalnych instrukcji języka maszynowego, które mogą być dostępne w bibliotece wykonawczej. Ponieważ operacje są atomowe, udostępnione dane są zawsze utrzymywane w prawidłowym stanie, bez względu na to, jakie inne wątki mają do nich dostęp. Operacje atomowe stanowią podstawę wielu mechanizmów blokowania wątków.

Czytaj więcej:

http://en.wikipedia.org/wiki/Thread_safety


Blauohr
źródło
4
W tym powiązaniu technicznie brakuje kilku krytycznych punktów. Wspomniana pamięć współużytkowana musi być zmienna (pamięć tylko do odczytu nie może być wątkowo niebezpieczna), a wiele wątków musi: a) wykonać wiele operacji zapisu na pamięci, podczas których pamięć jest niespójna (niewłaściwa) stan ib) pozwalają innym wątkom przerwać ten wątek, gdy pamięć jest niespójna.
Charles Bretana
20
kiedy wyszukiwanym Google pierwszym wynikiem jest wiki, nie ma sensu czynić go tutaj zbędnym.
Ranvir
Co masz na myśli „kod dostępu do funkcji”? Wykonywana sama funkcja jest kodem, prawda?
Koray Tugay
82

Kod bezpieczny dla wątków to kod, który działa, nawet jeśli wiele wątków wykonuje go jednocześnie.

http://mindprod.com/jgloss/threadsafe.html

Marek Blotny
źródło
34
W ramach tego samego procesu!
Ali Afshar,
Rzeczywiście, w tym samym procesie :)
Marek Blotny
4
„Pisanie kodu, który będzie działał stabilnie przez tygodnie, wymaga ekstremalnej paranoi”. To cytat, który lubię :)
Jim T
5
duh! ta odpowiedź po prostu przypomina pytanie! --- A dlaczego tylko w ramach tego samego procesu? Jeśli kod zawiedzie, gdy wiele wątków wykonuje go z różnych procesów, to prawdopodobnie („pamięć współdzielona” może znajdować się w pliku na dysku), NIE jest wątkowo bezpieczny !!
Charles Bretana
1
@ mg30rg. Być może zamieszanie wynika z jakiegoś myślenia, że ​​gdy blok kodu jest wykonywany przez wiele procesów, ale tylko przez jeden wątek na proces, że w jakiś sposób jest to scenariusz „jednowątkowy”, a nie scenariusz wielowątkowy . Ten pomysł nawet się nie myli. To tylko błędna definicja. Oczywiście, wiele procesów na ogół nie wykonuje się w tym samym wątku w sposób zsynchronizowany (z wyjątkiem rzadkich scenariuszy, w których procesy według projektu koordynują się ze sobą, a system operacyjny współdzieli wątki między procesami.)
Charles Bretana
50

Bardziej pouczające pytanie dotyczy tego, co sprawia, że ​​kod nie jest bezpieczny dla wątków, a odpowiedź jest taka, że ​​muszą być spełnione cztery warunki ... Wyobraź sobie następujący kod (i jego tłumaczenie na język maszynowy)

totalRequests = totalRequests + 1
MOV EAX, [totalRequests]   // load memory for tot Requests into register
INC EAX                    // update register
MOV [totalRequests], EAX   // store updated value back to memory
  1. Pierwszy warunek jest taki, że istnieją lokalizacje pamięci, które są dostępne z więcej niż jednego wątku. Zazwyczaj lokalizacje te są zmiennymi globalnymi / statycznymi lub są pamięcią sterty dostępną ze zmiennych globalnych / statycznych. Każdy wątek otrzymuje własną ramkę stosu dla zmiennych lokalnych o zasięgu funkcji / metody, więc te lokalne zmienne funkcji / metody, otoh, (które są na stosie) są dostępne tylko z jednego wątku, który jest właścicielem tego stosu.
  2. Drugi warunek polega na tym, że istnieje właściwość (często nazywana niezmiennikiem ), która jest powiązana z tymi lokalizacjami pamięci współużytkowanej, które muszą być prawdziwe lub prawidłowe, aby program działał poprawnie. W powyższym przykładzie właściwość jest taka, że ​​„ totalRequests musi dokładnie reprezentować całkowitą liczbę wykonań dowolnego wątku przez dowolną część instrukcji przyrostu ”. Zazwyczaj ta niezmienna właściwość musi mieć wartość true (w tym przypadku totalRequests musi zawierać dokładną liczbę), zanim nastąpi aktualizacja, aby aktualizacja była poprawna.
  3. Trzeci warunek jest taki, że właściwość niezmiennicza NIE zachowuje się podczas pewnej części faktycznej aktualizacji. (Jest to przejściowo nieważne lub fałszywe podczas pewnej części przetwarzania). W tym konkretnym przypadku, od momentu pobrania totalRequests do momentu zapisania zaktualizowanej wartości, totalRequests nie spełnia niezmiennika.
  4. Czwarty i ostatni warunek, który musi wystąpić, aby wyścig się odbył (i dlatego kod NIE może być „bezpieczny dla wątków”), to że inny wątek musi mieć dostęp do pamięci współużytkowanej, gdy niezmiennik jest zepsuty, co powoduje niespójność lub nieprawidłowe zachowanie.
Charles Bretana
źródło
6
Obejmuje to tylko tak zwane wyścigi danych i jest oczywiście ważne. Istnieją jednak inne sposoby, w których kod nie może być bezpieczny dla wątków - na przykład złe blokowanie, które może prowadzić do zakleszczenia. Nawet coś tak prostego jak wywołanie System.exit () gdzieś w wątku java sprawia, że ​​ten kod nie jest bezpieczny.
Ingo
2
Chyba do pewnego stopnia jest to semantyka, ale argumentowałbym, że zły kod blokujący, który może powodować zakleszczenie, nie czyni kodu niebezpiecznym. Po pierwsze, nie ma potrzeby blokowania kodu w pierwszej kolejności, chyba że możliwe są warunki wyścigu opisane powyżej. Następnie, jeśli napiszesz kod blokujący w sposób powodujący zakleszczenie, nie jest to niebezpieczne dla wątków, to po prostu zły kod.
Charles Bretana,
1
Należy jednak pamiętać, że impas nie wystąpi podczas uruchamiania jednowątkowego, więc dla większości z nas byłoby to zgodne z intuicyjnym znaczeniem (nie) „bezpiecznego wątku”.
Jon Coombs
Cóż, impasy nie mogą wystąpić, chyba że działasz na wielu wątkach, ale to tak, jakby powiedzieć, że problemy z siecią nie mogą się zdarzyć, jeśli pracujesz na jednym komputerze. Inne problemy mogą się również zdarzyć w przypadku jednowątkowego, jeśli programista napisze kod tak, aby wyłamał się z krytycznych linii kodu przed ukończeniem aktualizacji i zmodyfikuje zmienną w innym podprogramie.
Charles Bretana
34

Podoba mi się definicja z Briana Goetza z Java Concurrency in Practice ze względu na jej kompleksowość

„Klasa jest bezpieczna dla wątków, jeśli zachowuje się poprawnie, gdy uzyskuje się do niej dostęp 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. „

Buu Nguyen
źródło
Ta definicja jest niepełna i nieokreślona, ​​a na pewno nie wyczerpująca. Ile razy musi działać bezpiecznie, tylko raz? dziesięć razy? każdego razu? 80% czasu? i nie określa, co powoduje, że jest „Niebezpieczny”. Jeśli nie działa bezpiecznie, ale błąd był spowodowany błędem dzielenia przez zero, czy to powoduje, że wątek jest „Niebezpieczny”?
Charles Bretana
Następnym razem bądź bardziej uprzejmy i może możemy porozmawiać. To nie jest Reddit i nie mam ochoty rozmawiać z niegrzecznymi ludźmi.
Buu Nguyen,
Twoje interpretowanie komentarzy na temat definicji kogoś innego jako zniewagi wobec ciebie mówi. Zanim zareagujesz emocjonalnie, musisz przeczytać i zrozumieć treść. Nic nie różni się w moim komentarzu. Zwracałem uwagę na znaczenie definicji. Przepraszam, jeśli przykłady, które wykorzystałem do zilustrowania tego punktu, sprawiły, że poczułeś się niekomfortowo.
Charles Bretana,
28

Jak zauważyli inni, bezpieczeństwo wątków oznacza, że ​​fragment kodu będzie działał bez błędów, jeśli użyje go więcej niż jeden wątek jednocześnie.

Warto mieć świadomość, że czasami wiąże się to z kosztem czasu komputerowego i bardziej złożonego kodowania, więc nie zawsze jest to pożądane. Jeśli klasa może być bezpiecznie użyta tylko w jednym wątku, lepiej to zrobić.

Na przykład Java ma dwie klasy, które są prawie równoważne, StringBufferi StringBuilder. Różnica polega na tym, że StringBufferjest wątkowa, więc jedno wystąpienie StringBuffermoże być używane przez wiele wątków jednocześnie. StringBuildernie jest bezpieczny dla wątków i został zaprojektowany jako wydajniejszy zamiennik dla tych przypadków (zdecydowana większość), gdy Łańcuch jest zbudowany tylko z jednego wątku.

Marcus Downing
źródło
22

Kod bezpieczny dla wątku działa zgodnie ze specyfikacją, nawet jeśli jest wprowadzany jednocześnie przez różne wątki. Często oznacza to, że wewnętrzne struktury danych lub operacje, które powinny działać nieprzerwanie, są chronione przed różnymi modyfikacjami jednocześnie.

Memento
źródło
21

Łatwiejszym sposobem na zrozumienie tego jest to, że kod nie jest bezpieczny dla wątków. Istnieją dwa główne problemy, które powodują, że aplikacja wątkowa ma niepożądane zachowanie.

  • Dostęp do zmiennej współdzielonej bez blokowania
    Ta zmienna może być modyfikowana przez inny wątek podczas wykonywania funkcji. Chcesz temu zapobiec za pomocą mechanizmu blokującego, aby mieć pewność, że Twoja funkcja się zachowa. Ogólna zasada polega na utrzymywaniu blokady przez możliwie najkrótszy czas.

  • Zakleszczenie spowodowane wzajemną zależnością od zmiennej współdzielonej
    Jeśli masz dwie zmienne współdzielone A i B. W jednej funkcji najpierw blokujesz A, a następnie blokujesz B. W innej funkcji zaczynasz blokować B, a po pewnym czasie blokujesz A. jest potencjalnym impasem, w którym pierwsza funkcja będzie czekać na odblokowanie B, gdy druga funkcja będzie czekać na odblokowanie A. Ten problem prawdopodobnie nie wystąpi w twoim środowisku programistycznym i tylko od czasu do czasu. Aby tego uniknąć, wszystkie zamki muszą zawsze być w tej samej kolejności.

Hapkido
źródło
9

Tak i nie.

Bezpieczeństwo wątków to coś więcej niż tylko upewnienie się, że do twoich udostępnionych danych ma dostęp tylko jeden wątek na raz. Musisz zapewnić sekwencyjny dostęp do współużytkowanych danych, jednocześnie unikając warunków wyścigu , impasu , blokad żywych i głodu zasobów .

Nieprzewidywalne wyniki, gdy działa wiele wątków, nie są wymaganym warunkiem kodu zabezpieczającego wątki, ale często są produktem ubocznym. Na przykład możesz mieć schemat producent-konsument skonfigurowany ze wspólną kolejką, jednym wątkiem producenta i kilkoma wątkami konsumenta, a przepływ danych może być doskonale przewidywalny. Jeśli zaczniesz wprowadzać więcej klientów, zobaczysz więcej losowo wyglądających wyników.

Bill jaszczurka
źródło
9

Zasadniczo wiele rzeczy może pójść nie tak w środowisku wielowątkowym (zmiana kolejności instrukcji, częściowo skonstruowane obiekty, ta sama zmienna o różnych wartościach w różnych wątkach z powodu buforowania na poziomie procesora itp.).

Podoba mi się definicja podana przez Java Concurrency in Practice :

[Część kodu] jest bezpieczna dla wątków, jeśli zachowuje się poprawnie po uzyskaniu 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 kod telefoniczny.

Przez prawidłowe oznaczają, że program zachowuje się zgodnie ze specyfikacjami.

Wymyślony przykład

Wyobraź sobie, że wdrażasz licznik. Można powiedzieć, że zachowuje się poprawnie, jeśli:

  • counter.next() nigdy nie zwraca wartości, która została już wcześniej zwrócona (dla uproszczenia nie zakładamy przepełnienia itp.)
  • wszystkie wartości od 0 do bieżącej wartości zostały w pewnym momencie zwrócone (żadna wartość nie jest pomijana)

Licznik bezpieczny dla wątków działałby zgodnie z tymi regułami bez względu na to, ile wątków uzyskuje dostęp do niego jednocześnie (co zwykle nie byłoby przypadkiem naiwnej implementacji).

Uwaga: cross-post dla programistów

assylias
źródło
8

Po prostu - kod będzie działał poprawnie, jeśli wiele wątków wykonuje ten kod w tym samym czasie.

Greg Balajewicz
źródło
5

Nie myl bezpieczeństwa nici z determinizmem. Kod bezpieczny dla wątków może być również niedeterministyczny. Biorąc pod uwagę trudność debugowania problemów z kodem wątkowym, jest to prawdopodobnie normalny przypadek. :-)

Bezpieczeństwo wątków zapewnia po prostu, że gdy wątek modyfikuje lub odczytuje udostępnione dane, żaden inny wątek nie może uzyskać do niego dostępu w sposób, który zmienia dane. Jeśli Twój kod zależy od określonej kolejności wykonania dla poprawności, potrzebujesz innych mechanizmów synchronizacji poza tymi wymaganymi dla bezpieczeństwa wątków, aby to zapewnić.

tvanfosson
źródło
5

Chciałbym dodać więcej informacji na temat innych dobrych odpowiedzi.

Bezpieczeństwo wątków oznacza, że ​​wiele wątków może zapisywać / odczytywać dane w tym samym obiekcie bez błędów niespójności pamięci. W programie wielowątkowym program bezpieczny w wątku nie powoduje skutków ubocznych w udostępnianych danych .

Więcej informacji na ten temat SE:

Co znaczy Threadafe?

Program bezpieczny dla wątków gwarantuje spójność pamięci .

Ze strony dokumentacji Oracle w zaawansowanym współbieżnym interfejsie API:

Właściwości spójności pamięci:

Rozdział 17 specyfikacji języka Java ™ definiuje relację „dzieje się przed” operacjami pamięci, takimi jak odczyty i zapisy zmiennych współużytkowanych. Gwarantuje to, że wyniki zapisu przez jeden wątek będą widoczne dla odczytu przez inny wątek tylko wtedy, gdy nastąpi operacja zapisu przed operacją odczytu .

Konstrukty synchronizedi volatile, a także metody Thread.start()i Thread.join(), mogą tworzyć relacje przed wydarzeniami.

Metody wszystkich klas java.util.concurrenti podpakietów rozszerzają te gwarancje na synchronizację wyższego poziomu. W szczególności:

  1. Akcje w wątku przed umieszczeniem obiektu w dowolnej współbieżnej kolekcji mają miejsce przed czynnościami po dostępie lub usunięciu tego elementu z kolekcji w innym wątku.
  2. Działania w wątku przed zgłoszeniem Runnablezdarzenia do - Executorzanim rozpocznie się jego wykonanie. Podobnie w przypadku Callables przesłanych do ExecutorService.
  3. Działania podejmowane przez obliczenia asynchroniczne reprezentowane przez Futuredziałania poprzedzające po odzyskaniu wyniku Future.get()w innym wątku.
  4. Działania poprzedzające „zwolnienie” metod synchronizujących , takie jak Lock.unlock, Semaphore.release, and CountDownLatch.countDowndziałania wykonywane przed udaną metodą „akwizycji”, takie jak Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.awaitna tym samym obiekcie synchronizacyjnym w innym wątku.
  5. Dla każdej pary wątków, które z powodzeniem wymieniają obiekty za pośrednictwem Exchanger, mają miejsce akcje poprzedzające exchange()w każdym wątku - przed działaniami następującymi po odpowiedniej wymianie () w innym wątku.
  6. Akcje przed wywołaniem CyclicBarrier.awaiti Phaser.awaitAdvance(jak również jego warianty) zdarzają się - przed akcjami wykonywanymi przez akcję barierową, a akcje wykonywane przez akcję barierową - przed czynnościami po udanym powrocie z odpowiedniego oczekiwania czekają w innych wątkach.
Ravindra babu
źródło
4

Aby wypełnić inne odpowiedzi:

Synchronizacja to tylko zmartwienie, gdy kod w metodzie robi jedną z dwóch rzeczy:

  1. współpracuje z niektórymi zasobami zewnętrznymi, które nie są bezpieczne dla wątków.
  2. Czyta lub zmienia trwały obiekt lub pole klasy

Oznacza to, że zmienne zdefiniowane W twojej metodzie są zawsze wątkowo bezpieczne. Każde wywołanie metody ma własną wersję tych zmiennych. Jeśli metoda jest wywoływana przez inny wątek lub przez ten sam wątek, lub nawet jeśli metoda wywołuje się sama (rekurencja), wartości tych zmiennych nie są współużytkowane.

Nie można zagwarantować, że planowanie wątków będzie działać w trybie round-robin . Zadanie może całkowicie pochłonąć procesor kosztem wątków o tym samym priorytecie. Możesz użyć Thread.yield (), aby mieć sumienie. Możesz użyć (w java) Thread.setPriority (Thread.NORM_PRIORITY-1), aby obniżyć priorytet wątku

Dodatkowo uważaj na:

  • wysokie koszty działania (wspomniane już przez innych) w aplikacjach, które iterują te „bezpieczne dla wątków” struktury.
  • Thread.sleep (5000) ma spać przez 5 sekund. Jeśli jednak ktoś zmieni czas systemowy, możesz spać bardzo długo lub wcale. System operacyjny rejestruje czas budzenia w formie bezwzględnej, a nie względnej.
VonC
źródło
2

Tak i tak. Oznacza to, że dane nie są modyfikowane jednocześnie przez więcej niż jeden wątek. Jednak Twój program może działać zgodnie z oczekiwaniami i wydawać się bezpieczny dla wątków, nawet jeśli zasadniczo nie jest.

Należy pamiętać, że nieprzewidywalność wyników jest konsekwencją „warunków wyścigu”, które prawdopodobnie skutkują modyfikacją danych w kolejności innej niż oczekiwana.

Steve Knight
źródło
2

Odpowiedzmy na ten przykład:

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

countTo10Sposób dodaje jeden do licznika, a następnie zwraca wartość true, jeżeli liczba osiągnęła 10. Należy zwrócić tylko raz prawdziwe.

Działa to tak długo, jak długo tylko jeden wątek uruchamia kod. Jeśli dwa wątki uruchamiają kod w tym samym czasie, mogą wystąpić różne problemy.

Na przykład, jeśli liczenie zaczyna się od 9, jeden wątek może dodać 1 do zliczenia (co 10), ale następnie drugi wątek może wejść do metody i dodać 1 ponownie (co 11), zanim pierwszy wątek będzie miał szansę wykonać porównanie z 10 Następnie oba wątki dokonują porównania i stwierdzają, że liczba wynosi 11 i żaden nie zwraca wartości true.

Więc ten kod nie jest bezpieczny dla wątków.

Zasadniczo wszystkie problemy związane z wielowątkowością są spowodowane pewną odmianą tego rodzaju problemu.

Rozwiązaniem jest zapewnienie, aby dodanie i porównanie nie mogły być oddzielone (na przykład przez otoczenie dwóch instrukcji jakimś kodem synchronizacyjnym) lub przez opracowanie rozwiązania, które nie wymaga dwóch operacji. Taki kod byłby bezpieczny dla wątków.

rghome
źródło
1

Przynajmniej w C ++ myślę, że bezpieczny w wątkach jest trochę mylący, ponieważ pozostawia wiele poza nazwą. Aby być bezpiecznym dla wątków, kod zazwyczaj musi być proaktywny . Na ogół nie jest to cecha pasywna.

Aby klasa była bezpieczna dla bieżnika, musi mieć „dodatkowe” funkcje, które zwiększają obciążenie. Funkcje te są częścią implementacji klasy i ogólnie mówiąc, ukryte przed interfejsem. Oznacza to, że różne wątki mogą uzyskiwać dostęp do dowolnego członka klasy bez konieczności martwienia się o konflikt z dostępem równoległym przez inny wątek ORAZ mogą to robić w bardzo leniwy sposób, używając zwykłego starego, normalnego stylu kodowania, bez konieczności robienia tego wszystkie te szalone rzeczy związane z synchronizacją, które są już wbudowane w wnętrzności wywoływanego kodu.

I dlatego niektórzy ludzie wolą używać tego terminu zsynchronizowanego wewnętrznie .

Zestawy terminologiczne

Istnieją trzy główne zestawy terminologii dotyczącej tych pomysłów, które napotkałem. Pierwszą i historycznie bardziej popularną (ale gorszą) jest:

  1. wątek bezpieczny
  2. nie jest bezpieczny dla wątków

Drugi (i lepszy) to:

  1. dowód wątku
  2. kompatybilny z wątkami
  3. wątek wrogi

Trzeci to:

  1. wewnętrznie zsynchronizowany
  2. zsynchronizowane zewnętrznie
  3. niezsynchronizowany

Analogie

wątek bezpieczny ~ dowód wątku ~ zsynchronizowany wewnętrznie

Przykładem systemu zsynchronizowanego wewnętrznie (znanego również jako wątek bezpieczny lub odporny na gwint ) jest restauracja, w której gospodarz wita cię przy drzwiach i uniemożliwia kolejkowanie. Gospodarz jest częścią mechanizmu restauracji służącego do radzenia sobie z wieloma klientami i może zastosować pewne trudne sztuczki w celu optymalizacji miejsc siedzących oczekujących klientów, na przykład biorąc pod uwagę wielkość imprezy lub czas, na jaki wyglądają , a nawet przyjmowanie rezerwacji przez telefon. Restauracja jest wewnętrznie zsynchronizowana, ponieważ wszystko to jest częścią interfejsu do interakcji z nią.

nie jest bezpieczny (ale fajny) ~ kompatybilny z wątkiem ~ zsynchronizowany zewnętrznie ~ z wolnym wątkiem

Załóżmy, że idziesz do banku. Istnieje linia, czyli spór dla kasjerów bankowych. Ponieważ nie jesteś dzikim, zdajesz sobie sprawę, że najlepszą rzeczą, jaką można zrobić w trakcie rywalizacji o zasoby, jest stanie w kolejce jak cywilizowana istota. Nikt technicznie tego nie zmusza. Mamy nadzieję, że masz niezbędne programy społecznościowe, aby zrobić to sam. W tym sensie lobby bankowe jest zsynchronizowane zewnętrznie. Czy powinniśmy powiedzieć, że jest to niebezpieczne? to implikuje to, jeśli korzystasz z zestawu terminologii bipolarnej bezpiecznej dla wątku , która nie jest bezpieczna dla wątku . To niezbyt dobry zestaw warunków. Lepsza terminologia to zsynchronizowana zewnętrznie,Lobby bankowe nie jest wrogie dla wielu klientów, ale też nie synchronizuje ich. Klienci robią to sami.

Nazywa się to również „wolnym wątkiem”, gdzie „wolny” jest jak w „wolnym od wszy” - lub w tym przypadku zamki. Dokładniej mówiąc, prymitywy synchronizacji. To nie znaczy, że kod może działać na wielu wątkach bez tych prymitywów. Oznacza to po prostu, że nie ma go już zainstalowanego i to Ty, użytkownik kodu, musisz zainstalować je sam, jak uznasz za stosowne. Instalowanie własnych prymitywów synchronizacji może być trudne i wymaga intensywnego myślenia o kodzie, ale może także prowadzić do najszybszego możliwego programu, umożliwiając dostosowanie sposobu działania programu na dzisiejszych procesorach z przeplotem.

nie wątek bezpieczny (i zły) ~ wątek wrogi ~ niezsynchronizowany

Przykładem codziennej analogii do systemu wrogiego wątkom jest szarpnięcie samochodu sportowego, który odmawia korzystania z kierunkowskazów i nie chce zmieniać pasów. Ich styl jazdy jest wrogi gwint lub unsychronizable ponieważ nie ma sposobu, aby skoordynować z nimi, a to może prowadzić do rywalizacji na tym samym pasie ruchu, bez rezolucji, a tym samym przypadkiem jak dwa samochody próbować zajmować tę samą przestrzeń, bez protokołu do zapobiec temu. Ten wzorzec można również traktować szerzej jako antyspołeczny, co wolę, ponieważ jest mniej specyficzny dla wątków, a więc bardziej ogólnie dotyczy wielu dziedzin programowania.

Dlaczego Thread Safe i in. są złym zestawem terminologii

Pierwszy i najstarszy zestaw terminologii nie czyni dokładniejszego rozróżnienia między wrogością wątków a kompatybilnością wątków . Kompatybilność wątków jest bardziej pasywna niż tak zwane bezpieczeństwo wątków, ale to nie znaczy, że wywoływany kod jest niebezpieczny dla jednoczesnego użycia wątków. Oznacza to po prostu, że pasywna jest synchronizacja, która by na to pozwoliła, odkładając go na kod wywołujący, zamiast dostarczać go jako część jego wewnętrznej implementacji. Kompatybilny z wątkami to sposób, w jaki kod powinien być domyślnie zapisywany w większości przypadków, ale niestety jest to często błędnie uważane za niebezpieczne w wątku, tak jakby z natury było anty-bezpieczne, co jest głównym problemem dla programistów.

UWAGA: Wiele podręczników oprogramowania faktycznie używa terminu „bezpieczny dla wątków” w odniesieniu do „kompatybilnych z wątkami”, co jeszcze bardziej dezorientuje to, co już było bałaganem! Z tego właśnie powodu unikam terminów „bezpieczny dla wątków” i „bezpieczny dla wątków”, ponieważ niektóre źródła nazywają coś „bezpiecznym dla wątków”, podczas gdy inne nazywają to „niebezpiecznym dla wątków”, ponieważ nie mogą się zgodzić na temat tego, czy musisz spełnić dodatkowe standardy bezpieczeństwa (operacje podstawowe synchronizacji), czy po prostu NIE być wrogo nastawionym, aby zostać uznanym za „bezpieczny”. Unikaj więc tych warunków i używaj inteligentniejszych terminów, aby uniknąć niebezpiecznej nieporozumienia z innymi inżynierami.

Przypomnienie o naszych celach

Zasadniczo naszym celem jest obalenie chaosu.

Robimy to, tworząc deterministyczne systemy, na których możemy polegać. Determinizm jest drogi, głównie ze względu na koszty alternatywne utraty równoległości, potoków i ponownego zamawiania. Staramy się minimalizować determinizm, aby utrzymać nasze koszty na niskim poziomie, unikając przy tym podejmowania decyzji, które jeszcze bardziej osłabią ten niewielki determinizm, na jaki możemy sobie pozwolić.

Synchronizacja wątków polega na zwiększeniu porządku i zmniejszeniu chaosu. Poziomy, na których to robisz, odpowiadają wyżej wymienionym warunkom. Najwyższy poziom oznacza, że ​​system zachowuje się w sposób całkowicie przewidywalny za każdym razem. Drugi poziom oznacza, że ​​system zachowuje się na tyle dobrze, że wywołanie kodu może niezawodnie wykryć nieprzewidywalność. Na przykład fałszywy pobudka w zmiennej warunkowej lub błąd zablokowania muteksu, ponieważ nie jest on gotowy. Trzeci poziom oznacza, że ​​system nie zachowuje się wystarczająco dobrze, aby grać z kimkolwiek innym i może być NIGDY uruchamiany tylko w jednym wątku bez wywoływania chaosu.

Daniel Russell
źródło
1

Zamiast myśleć o kodzie lub klasach jako bezpiecznych dla wątków, czy nie, myślę, że bardziej pomocne jest myślenie o działaniach jako bezpiecznych dla wątków. Dwie akcje są bezpieczne dla wątków, jeśli będą zachowywać się tak, jak określono podczas uruchamiania z dowolnych kontekstów wątków. W wielu przypadkach klasy będą obsługiwały niektóre kombinacje działań w sposób bezpieczny dla wątków, a inne nie.

Na przykład wiele kolekcji, takich jak listy tablic i zestawy skrótów, zagwarantuje, że jeśli początkowo są one dostępne wyłącznie za pomocą jednego wątku i nigdy nie zostaną zmodyfikowane po tym, jak odniesienie stanie się widoczne dla innych wątków, można je odczytać dowolnie za pomocą dowolnej kombinacji wątków bez ingerencji.

Co ciekawsze, niektóre zbiory zestawów skrótów, takie jak oryginalne nie-ogólne zbiory w .NET, mogą oferować gwarancję, że dopóki żaden element nie zostanie nigdy usunięty, i pod warunkiem, że zapisuje do nich tylko jeden wątek, każdy wątek, który próbuje czytaj kolekcja będzie zachowywać się tak, jakby uzyskiwała dostęp do kolekcji, w której aktualizacje mogą być opóźnione i występować w dowolnej kolejności, ale w przeciwnym razie będą zachowywać się normalnie. Jeśli wątek nr 1 dodaje X, a następnie Y, a wątek nr 2 szuka i widzi Y, a następnie X, byłoby możliwe, aby wątek nr 2 zobaczył, że Y istnieje, ale X nie; to, czy takie zachowanie jest „bezpieczne dla wątków”, zależy od tego, czy wątek nr 2 jest przygotowany na poradzenie sobie z tą możliwością.

Na koniec, niektóre klasy - zwłaszcza blokujące biblioteki komunikacyjne - mogą mieć metodę „close” lub „Dispose”, która jest bezpieczna dla wątków w odniesieniu do wszystkich innych metod, ale nie ma innych metod, które są bezpieczne dla wątków w odniesieniu do wzajemnie. Jeśli wątek wykona blokujące żądanie odczytu, a użytkownik programu kliknie „anuluj”, nie będzie możliwości wysłania żądania zamknięcia przez wątek, który próbuje wykonać odczyt. Żądanie zamknięcia / usunięcia może jednak asynchronicznie ustawić flagę, co spowoduje, że żądanie odczytu zostanie anulowane jak najszybciej. Po zamknięciu dowolnego wątku obiekt stałby się bezużyteczny, a wszystkie próby przyszłych działań zakończyłyby się niepowodzeniem natychmiast,

supercat
źródło
0

W najprostszych słowach: P Jeśli bezpieczne jest wykonywanie wielu wątków w bloku kodu, jest ono bezpieczne dla wątków *

* obowiązują warunki

Warunki są wymienione przez inne odpowiedzi, takie jak 1. Wynik powinien być taki sam, jeśli wykonasz jeden wątek lub wiele wątków nad nim itp.

brudny
źródło