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?
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?
Odpowiedzi:
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
w języku niemieckim: http://de.wikipedia.org/wiki/Threadsicherheit
w języku francuskim: http://fr.wikipedia.org/wiki/Threadsafe (bardzo krótko)
Kod bezpieczny dla wątków to kod, który działa, nawet jeśli wiele wątków wykonuje go jednocześnie.
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
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. „
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, StringBuffer
i StringBuilder
. Różnica polega na tym, że StringBuffer
jest wątkowa, więc jedno wystąpienie StringBuffer
może być używane przez wiele wątków jednocześnie. StringBuilder
nie 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.
Ł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.
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.
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.)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
Po prostu - kod będzie działał poprawnie, jeśli wiele wątków wykonuje ten kod w tym samym czasie.
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ć.
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:
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 synchronized
i volatile
, a także metody Thread.start()
i Thread.join()
, mogą tworzyć relacje przed wydarzeniami.
Metody wszystkich klas java.util.concurrent
i podpakietów rozszerzają te gwarancje na synchronizację wyższego poziomu. W szczególności:
Runnable
zdarzenia do - Executor
zanim rozpocznie się jego wykonanie. Podobnie w przypadku Callables przesłanych do ExecutorService
.Future
działania poprzedzające po odzyskaniu wyniku Future.get()
w innym wątku.Lock.unlock, Semaphore.release, and CountDownLatch.countDown
działania wykonywane przed udaną metodą „akwizycji”, takie jak Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await
na tym samym obiekcie synchronizacyjnym w innym wątku.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.CyclicBarrier.await
i 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.Aby wypełnić inne odpowiedzi:
Synchronizacja to tylko zmartwienie, gdy kod w metodzie robi jedną z dwóch rzeczy:
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:
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.
Odpowiedzmy na ten przykład:
class NonThreadSafe {
private int counter = 0;
public boolean countTo10() {
count = count + 1;
return (count == 10);
}
countTo10
Sposó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.
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 .
Istnieją trzy główne zestawy terminologii dotyczącej tych pomysłów, które napotkałem. Pierwszą i historycznie bardziej popularną (ale gorszą) jest:
Drugi (i lepszy) to:
Trzeci to:
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.
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.
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.
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,
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.