Jestem nowy w wielowątkowości i próbowałem zrozumieć, jak działają muteksy. Dużo szukałem w Google, ale nadal pozostawiało wątpliwości, jak to działa, ponieważ stworzyłem własny program, w którym blokowanie nie działało.
Jedna absolutnie nieintuicyjna składnia muteksu polega na tym pthread_mutex_lock( &mutex1 );
, że wygląda na to, że mutex jest blokowany, a tym, co naprawdę chcę zablokować, jest inna zmienna. Czy ta składnia oznacza, że zablokowanie muteksu blokuje region kodu, dopóki mutex nie zostanie odblokowany? Skąd więc wątki wiedzą, że region jest zablokowany? [ UPDATE: Wątki wiedzą, że region jest zamknięty przez Memory Fencing ]. A czy nie można takiego zjawiska nazwać sekcją krytyczną? [ AKTUALIZACJA: Obiekty sekcji krytycznych są dostępne tylko w systemie Windows, gdzie obiekty są szybsze niż muteksy i są widoczne tylko dla wątku, który je implementuje. W przeciwnym razie sekcja krytyczna odnosi się tylko do obszaru kodu chronionego przez mutex ]
Krótko mówiąc, czy mógłbyś pomóc w najprostszym możliwym przykładowym programie mutex i najprostszym możliwym wyjaśnieniu logiki jego działania? Jestem pewien, że pomoże to wielu innym początkującym.
Odpowiedzi:
Oto moja skromna próba wyjaśnienia koncepcji nowicjuszom na całym świecie: ( wersja oznaczona kolorami również na moim blogu)
Wiele osób biegnie do samotnej budki telefonicznej (nie mają telefonów komórkowych), aby porozmawiać z bliskimi. Pierwszą osobą, która złapie klamkę w budce, jest ta, która może użyć telefonu. Musi trzymać klamkę drzwi tak długo, jak używa telefonu, inaczej ktoś inny złapie za klamkę, wyrzuci go i porozmawia z żoną :) Nie ma systemu kolejek jako takiego. Kiedy osoba zakończy rozmowę, wyjdzie z budki i opuści klamkę, następna osoba, która złapie klamkę, będzie mogła używać telefonu.
Wątek jest: Każda osoba mutex wynosi: klamka blokady jest: ręka osoby zasób jest: telefon
Każdy wątek, który ma wykonać jakieś linie kodu, które nie powinny być modyfikowane przez inne wątki w tym samym czasie (używanie telefonu do rozmowy z żoną), musi najpierw uzyskać blokadę na muteksie (ściskając klamkę drzwi budki ). Dopiero wtedy wątek będzie mógł uruchomić te linie kodu (wykonując połączenie telefoniczne).
Gdy wątek wykona ten kod, powinien zwolnić blokadę muteksu, aby inny wątek mógł uzyskać blokadę muteksu (inne osoby mogą uzyskać dostęp do budki telefonicznej).
[ Koncepcja posiadania muteksu jest nieco absurdalna, biorąc pod uwagę wyłączny dostęp w świecie rzeczywistym, ale wydaje mi się, że w świecie programowania nie było innego sposobu, aby inne wątki „zobaczyły”, że wątek wykonywał już pewne linie kodu. Istnieją koncepcje rekurencyjnych muteksów itp., Ale ten przykład miał jedynie na celu pokazanie podstawowej koncepcji. Mam nadzieję, że przykład daje jasny obraz koncepcji. ]
Z wątkami C ++ 11:
Skompiluj i uruchom przy użyciu
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Zamiast jawnie używać
lock
iunlock
, możesz użyć nawiasów, jak pokazano tutaj , jeśli używasz blokady o określonym zakresie ze względu na zalety, jakie zapewnia . Zamki z lunetą mają jednak niewielki narzut wydajności.źródło
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. Jeśli chodzi o blokowanie zakresu, zależy to od programisty, w zależności od rodzaju aplikacji, którą budują. Ta odpowiedź miała na celu odniesienie się do podstawowego zrozumienia koncepcji mutexu, a nie wniknięcie we wszystkie jej zawiłości, więc twoje komentarze i linki są mile widziane, ale trochę wykraczają poza zakres tego samouczka.Chociaż mutex może być używany do rozwiązywania innych problemów, głównym powodem ich istnienia jest zapewnienie wzajemnego wykluczenia, a tym samym rozwiązanie tego, co jest znane jako stan rasy. Kiedy dwa (lub więcej) wątki lub procesy próbują uzyskać dostęp do tej samej zmiennej jednocześnie, mamy możliwość wystąpienia sytuacji wyścigu. Rozważmy następujący kod
Elementy wewnętrzne tej funkcji wyglądają tak prosto. To tylko jedno stwierdzenie. Jednak typowym odpowiednikiem języka asemblera może być:
Ponieważ wszystkie równoważne instrukcje języka asemblera są wymagane do wykonania operacji inkrementacji na i, mówimy, że inkrementacja i nie jest operacją atmosferyczną. Operacja atomowa to taka, którą można zakończyć na sprzęcie z gwarancją braku przerwania po rozpoczęciu wykonywania instrukcji. Przyrost i składa się z łańcucha 3 instrukcji atomowych. W systemie współbieżnym, w którym kilka wątków wywołuje funkcję, pojawiają się problemy, gdy wątek odczytuje lub zapisuje w niewłaściwym czasie. Wyobraź sobie, że mamy dwa wątki działające jednocześnie i jeden wywołuje funkcję bezpośrednio po drugim. Powiedzmy również, że zainicjowaliśmy i na 0. Załóżmy również, że mamy dużo rejestrów i że dwa wątki używają zupełnie innych rejestrów, więc nie będzie kolizji. Rzeczywisty czas tych wydarzeń może być:
Zdarzyło się, że mamy dwa wątki zwiększające się jednocześnie i, nasza funkcja jest wywoływana dwukrotnie, ale wynik jest niezgodny z tym faktem. Wygląda na to, że funkcja została wywołana tylko raz. Dzieje się tak, ponieważ atomowość jest „zepsuta” na poziomie maszyny, co oznacza, że wątki mogą się wzajemnie przerywać lub współpracować w niewłaściwych momentach.
Potrzebujemy mechanizmu, aby rozwiązać ten problem. Musimy trochę uporządkować powyższe instrukcje. Jednym z powszechnych mechanizmów jest blokowanie wszystkich wątków oprócz jednego. Mutex Pthread wykorzystuje ten mechanizm.
Każdy wątek, który musi wykonać pewne wiersze kodu, które mogą w niebezpieczny sposób modyfikować wspólne wartości przez inne wątki w tym samym czasie (używając telefonu do rozmowy z żoną), powinien najpierw uzyskać blokadę muteksu. W ten sposób każdy wątek wymagający dostępu do udostępnionych danych musi przejść przez blokadę mutex. Dopiero wtedy wątek będzie mógł wykonać kod. Ta sekcja kodu nazywana jest sekcją krytyczną.
Gdy wątek wykona sekcję krytyczną, powinien zwolnić blokadę muteksu, aby inny wątek mógł uzyskać blokadę muteksu.
Koncepcja posiadania muteksu wydaje się nieco dziwna, gdy rozważa się ludzi poszukujących wyłącznego dostępu do rzeczywistych, fizycznych obiektów, ale podczas programowania musimy być zamierzeni. Współbieżne wątki i procesy nie mają takiego społecznego i kulturowego wychowania, jak my, dlatego musimy zmusić je do ładnego udostępniania danych.
Więc technicznie rzecz biorąc, jak działa muteks? Czy nie cierpi z powodu tych samych warunków wyścigu, o których wspominaliśmy wcześniej? Czy pthread_mutex_lock () nie jest nieco bardziej skomplikowany niż zwykły przyrost zmiennej?
Technicznie rzecz biorąc, potrzebujemy wsparcia sprzętowego, aby nam pomóc. Projektanci sprzętu przekazują nam instrukcje maszynowe, które robią więcej niż jedną rzecz, ale gwarantują, że są atomowe. Klasycznym przykładem takiej instrukcji jest test-i-ustaw (TAS). Próbując uzyskać blokadę zasobu, możemy użyć TAS może sprawdzić, czy wartość w pamięci wynosi 0. Jeśli tak, to byłby to nasz sygnał, że zasób jest używany i nic nie robimy (lub dokładniej , czekamy według jakiegoś mechanizmu. Muteks pthreads umieści nas w specjalnej kolejce w systemie operacyjnym i powiadomi nas, gdy zasób stanie się dostępny. Głupsze systemy mogą wymagać od nas wykonania ciasnej pętli spinowej, testując stan w kółko) . Jeśli wartość w pamięci nie jest równa 0, TAS ustawia lokalizację na inną niż 0 bez używania innych instrukcji. To' to jak połączenie dwóch instrukcji asemblacji w 1, aby uzyskać atomowość. Zatem testowanie i zmiana wartości (jeśli zmiana jest odpowiednia) nie może zostać przerwana po rozpoczęciu. Na podstawie takiej instrukcji możemy zbudować muteksy.
Uwaga: niektóre sekcje mogą wyglądać podobnie do wcześniejszej odpowiedzi. Przyjąłem jego zaproszenie do redagowania, wolał oryginalny sposób, więc zachowuję to, co miałem, co jest nasycone odrobiną jego słownictwa.
źródło
Najlepszy samouczek dotyczący wątków, o którym wiem, jest tutaj:
https://computing.llnl.gov/tutorials/pthreads/
Podoba mi się, że jest napisane o API, a nie o konkretnej implementacji, i podaje kilka ładnych, prostych przykładów, które pomogą ci zrozumieć synchronizację.
źródło
Niedawno natknąłem się na ten post i myślę, że potrzebuje on zaktualizowanego rozwiązania dla muteksu c ++ 11 biblioteki standardowej (mianowicie std :: mutex).
Poniżej wkleiłem kod (pierwsze kroki z muteksem - nauczyłem się współbieżności na win32 z HANDLE, SetEvent, WaitForMultipleObjects itp.).
Ponieważ to moja pierwsza próba z std :: mutex i przyjaciółmi, chciałbym zobaczyć komentarze, sugestie i ulepszenia!
źródło
Funkcja
pthread_mutex_lock()
albo nabywa mutex dla wątku wywołującego lub bloki gwint do mutex mogą być nabyte. Powiązanepthread_mutex_unlock()
uwalnia mutex.Pomyśl o muteksie jak o kolejce; każdy wątek, który będzie próbował uzyskać muteks, zostanie umieszczony na końcu kolejki. Kiedy wątek zwalnia muteks, następny wątek w kolejce jest odłączany i jest teraz uruchomiony.
Krytyczny punkt odnosi się do regionu kodu, w którym nie jest możliwe determinizm. Często dzieje się tak, ponieważ wiele wątków próbuje uzyskać dostęp do wspólnej zmiennej. Sekcja krytyczna nie jest bezpieczna, dopóki nie nastąpi synchronizacja. Blokada mutex jest jedną z form synchronizacji.
źródło
Powinieneś sprawdzić zmienną mutex przed użyciem obszaru chronionego przez mutex. Więc twoja pthread_mutex_lock () może (w zależności od implementacji) czekać, aż mutex1 zostanie zwolniony lub zwrócić wartość wskazującą, że nie można było uzyskać blokady, jeśli ktoś inny już ją zablokował.
Mutex to tak naprawdę uproszczony semafor. Jeśli o nich czytasz i je rozumiesz, rozumiesz muteksy. Istnieje kilka pytań dotyczących muteksów i semaforów w SO. Różnica między binarnym semaforem a muteksem , kiedy powinniśmy używać muteksu, a kiedy semafora i tak dalej. Przykład toalety w pierwszym linku jest tak dobrym przykładem, jak tylko można sobie wyobrazić. Cały kod służy do sprawdzenia, czy klucz jest dostępny, a jeśli jest, rezerwuje go. Zwróć uwagę, że tak naprawdę nie rezerwujesz samej toalety, ale klucz.
źródło
pthread_mutex_lock
nie może wrócić, jeśli ktoś inny przytrzyma zamek. W tym przypadku blokuje i o to chodzi.pthread_mutex_trylock
to funkcja, która powróci po przytrzymaniu blokady.Dla tych, którzy szukają przykładu shortex mutex:
źródło
PRZYKŁAD SEMAPHORU:
Źródła: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
źródło