Załóżmy, że dziesięć osób musiało dzielić długopis (może pracują w naprawdę spieniężonej firmie). Ponieważ muszą pisać długimi dokumentami za pomocą pióra, ale większość pracy przy pisaniu dokumentu polega jedynie na tym, co powiedzieć, zgadzają się, że każda osoba używa pióra do napisania jednego zdania dokumentu, a następnie musi udostępniać ją reszcie grupy.
Teraz mamy problem: co zrobić, jeśli dwoje ludzi myśli o następnym zdaniu i obaj chcą używać pióra naraz? Moglibyśmy powiedzieć, że obaj ludzie mogą chwycić pióro, ale jest to delikatny stary długopis, więc jeśli dwoje ludzi go złapie, pęknie. Zamiast tego narysujemy kredą linię wokół pióra. Najpierw kładziesz rękę na linii kredy, następnie chwytasz pióro. Jeśli dłoń jednej osoby znajduje się wewnątrz linii kredy, nikt inny nie może włożyć ręki do linii kredy. Jeśli dwie osoby spróbują położyć rękę na linii kredy w tym samym czasie, zgodnie z tymi zasadami, tylko jeden z nich dostanie się najpierw do linii kredy, więc drugi musi odciągnąć rękę i trzymać ją tuż poza linią kredy pióro jest znów dostępne.
Powiążmy to z powrotem z muteksami. Mutex to sposób na ochronę wspólnego zasobu (pióra) przez krótki czas, nazywany krytycznym odcinkiem (czas napisania jednego zdania dokumentu). Za każdym razem, gdy chcesz korzystać z zasobu, zgadzasz się najpierw zadzwonić pod numer mutex_lock
(włóż rękę w linię kredy). Kiedy skończysz z zasobem, zgadzasz się zadzwonić pod numer mutex_unlock
(wyciągnij rękę z obszaru linii kredy).
Teraz, jak implementowane są muteksy. Muteks jest zwykle implementowany z pamięcią wspólną. Istnieje pewien wspólny, nieprzejrzysty obiekt danych o nazwie mutex, a funkcje mutex_lock
iobie przyjmują wskaźnik do jednego z nich. Funkcja mutex_lock
sprawdza i modyfikuje dane w obrębie muteksu za pomocą instrukcji atomowej, testuj i ładuj/przechowuj-warunkowe (często używana jest funkcja x86, xhcg
) i "przejmuje muteks" - ustawia zawartość obiekt mutex wskazujący innym wątkom, że sekcja krytyczna jest zablokowana - lub musi czekać. W końcu wątek dostaje muteks, wykonuje pracę w krytycznej sekcji i wywołuje mutex_unlock
. Ta funkcja ustawia dane wewnątrz muteksu, aby oznaczyć je jako dostępne, i prawdopodobnie budzi śpiące wątki, które próbują uzyskać muteks (zależy to od implementacji mutex - niektóre implementacje z xchg
wirują w ciasnym stylu na mutex jest dostępny, więc nie ma potrzeby, aby mutex_unlock
powiadamiał kogokolwiek).
Dlaczego blokowanie mutex byłoby szybsze niż wyjście do pamięci? W skrócie, buforowanie. Procesor ma pamięć podręczną, do której można uzyskać bardzo szybki dostęp, więc operacja xchg
nie musi przechodzić do pamięci tak długo, jak długo procesor może zagwarantować, że żaden inny procesor nie uzyska dostępu do tych danych.Ale x86 ma pojęcie "posiadania" linii pamięci podręcznej - jeśli procesor 0 jest właścicielem linii pamięci podręcznej, każdy inny procesor, który chce korzystać z danych w tej linii pamięci podręcznej, musi przejść przez procesor 0. W ten sposób nie ma potrzeby stosowania xhcg
operacja, aby przeglądać dowolne dane poza pamięcią podręczną, a dostęp do pamięci podręcznej jest zazwyczaj bardzo szybki, więc uzyskanie niekwestionowanego muteksu jest szybsze niż dostęp do pamięci.
Jest jednak jedno zastrzeżenie do tego ostatniego paragrafu: zasiłek prędkości obowiązuje tylko dla niezablokowanej blokady mutex . Jeśli dwa wątki próbują zablokować ten sam muteks w tym samym czasie, procesory, które uruchamiają te wątki, muszą się komunikować i zajmować własnością odpowiedniej linii pamięci podręcznej, co znacznie spowalnia pozyskiwanie muteksów. Ponadto, jeden z dwóch wątków będzie musiał poczekać, aż drugi wątek wykona kod w sekcji krytycznej, a następnie zwolni muteksa, dodatkowo spowalniając akwizycję muteksa dla jednego z wątków.
Nie rozumiem twojego zdziwienia. blokada mutex jest wykonywana na danych "zmiana" (zapis), podczas gdy pobieranie oznacza "odczyt". kiedy nikt nie nabywa muteksa, nic nie robi (dzieje się tak, gdy wykonywane są operacje "odczytu"). czy coś mi brakuje? – alfasin