Kiedy należy stosować blokadę zamiast muteksu?

294

Myślę, że obaj wykonują tę samą pracę, jak zdecydować, którego użyć do synchronizacji?

wentylator kompilacji
źródło
1
możliwy duplikat Spinlock kontra semafor!
Paul R
11
Mutex i semafor to nie to samo, więc nie sądzę, że to duplikat. Odpowiedź przywoływanego artykułu stwierdza to poprawnie. Aby uzyskać więcej informacji, zobacz barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
nanoquack

Odpowiedzi:

729

Teoria

Teoretycznie, gdy wątek próbuje zablokować muteks i nie powiedzie się, ponieważ muteks jest już zablokowany, przejdzie w tryb uśpienia, natychmiast umożliwiając uruchomienie innego wątku. Będzie on spał aż do przebudzenia, co będzie miało miejsce, gdy muteks zostanie odblokowany przez dowolny wątek, który wcześniej blokował zamek. Gdy wątek próbuje zablokować blokadę spinową, ale to się nie udaje, będzie ciągle próbował ją blokować, aż w końcu się powiedzie; dlatego nie pozwoli na zastąpienie innego wątku (jednak system operacyjny wymusi przełączenie na inny wątek, oczywiście po przekroczeniu kwantowego czasu pracy procesora w bieżącym wątku).

Problem

Problem z muteksami polega na tym, że uśpienie wątków i ponowne ich przebudzenie są dość kosztownymi operacjami, będą wymagały sporo instrukcji procesora, a tym samym zajmą trochę czasu. Jeśli teraz muteks był zamknięty tylko na bardzo krótki czas, czas poświęcony na uśpienie nici i ponowne jej obudzenie może przekroczyć czas, w którym nić faktycznie spała, a nawet może przekroczyć czas, w którym nić zmarnowane przez ciągłe odpytywanie o blokadę. Z drugiej strony odpytywanie o blokadę będzie stale marnowało czas procesora, a jeśli blokada będzie utrzymywana przez dłuższy czas, zmarnuje to znacznie więcej czasu procesora i byłoby znacznie lepiej, gdyby zamiast tego wątek spał.

Rozwiązanie

Używanie spinlocków w systemie jednordzeniowym / jednoprocesorowym zwykle nie ma sensu, ponieważ dopóki odpytywanie blokad blokuje jedyny dostępny rdzeń procesora, żaden inny wątek nie może działać, a ponieważ żaden inny wątek nie może działać, blokada nie będzie działać bądź odblokowany. IOW, spinlock marnuje tylko czas procesora na tych systemach, bez realnych korzyści. Jeśli zamiast tego wątek zostałby uśpiony, inny wątek mógłby uruchomić się natychmiast, prawdopodobnie odblokowując blokadę, a następnie umożliwiając kontynuowanie przetwarzania pierwszego wątku, gdy ponownie się obudzi.

W systemach wielordzeniowych / wieloprocesorowych, z dużą ilością blokad, które są utrzymywane tylko przez bardzo krótki czas, czas tracony na ciągłe uśpienie wątków i wznawianie ich ponownie może znacznie zmniejszyć wydajność środowiska uruchomieniowego. Używając zamiast tego spinlocków, wątki mają szansę wykorzystać pełne kwanty uruchomieniowe (zawsze blokują się tylko na bardzo krótki czas, ale natychmiast kontynuują pracę), co prowadzi do znacznie większej przepustowości przetwarzania.

Praktyka

Ponieważ bardzo często programiści nie mogą z góry wiedzieć, czy muteksy lub blokady będą lepsze (np. Ponieważ liczba rdzeni procesora architektury docelowej jest nieznana), ani też systemy operacyjne nie mogą wiedzieć, czy pewien fragment kodu został zoptymalizowany pod kątem pojedynczego rdzenia lub w środowiskach wielordzeniowych większość systemów nie rozróżnia ściśle muteksów i spinlocków. W rzeczywistości większość współczesnych systemów operacyjnych ma hybrydowe muteksy i hybrydowe blokady. Co to właściwie znaczy?

Hybrydowy muteks początkowo zachowuje się jak spinlock w systemie wielordzeniowym. Jeśli wątek nie może zablokować muteksu, nie zostanie natychmiast uśpiony, ponieważ muteks może wkrótce zostać odblokowany, więc zamiast tego muteks najpierw zachowa się dokładnie jak spinlock. Tylko jeśli blokada nadal nie została uzyskana po pewnym czasie (lub ponownych próbach lub jakimkolwiek innym czynniku pomiarowym), nić jest naprawdę uśpiona. Jeśli ten sam kod działa w systemie z jednym rdzeniem, muteks nie będzie się blokował, jednak, jak widać powyżej, nie byłoby to korzystne.

Hybrydowa blokada początkowo zachowuje się jak normalna, ale aby uniknąć marnowania zbyt dużej ilości czasu procesora, może mieć strategię wycofywania. Zwykle nie powoduje uśpienia nici (ponieważ nie chcesz, aby tak się stało podczas używania blokady), ale może zdecydować o zatrzymaniu wątku (natychmiast lub po pewnym czasie) i zezwolić na uruchomienie innego wątku , zwiększając w ten sposób szanse na odblokowanie blokady (czysty przełącznik nici jest zwykle tańszy niż taki, który polega na uśpieniu i ponownym przebudzeniu nitki później, choć nie za daleko).

Podsumowanie

W razie wątpliwości użyj muteksów, są one zwykle lepszym wyborem, a większość nowoczesnych systemów pozwoli im na zablokowanie na bardzo krótki czas, jeśli wydaje się to korzystne. Używanie blokad może czasem poprawić wydajność, ale tylko pod pewnymi warunkami, a masz wątpliwości, co raczej mówi mi, że nie pracujesz obecnie nad żadnym projektem, w którym blokada może być korzystna. Możesz rozważyć użycie własnego „obiektu blokady”, który może wewnętrznie używać blokady lub muteksu (np. Takie zachowanie może być konfigurowalne podczas tworzenia takiego obiektu), początkowo używaj muteksów wszędzie i jeśli uważasz, że użycie blokady może być gdzieś naprawdę pomóżcie, spróbujcie porównać wyniki (np. używając profilera), ale pamiętajcie o przetestowaniu obu przypadków,

Aktualizacja: Ostrzeżenie dla iOS

Właściwie nie jest specyficzny dla iOS, ale iOS jest platformą, na której większość programistów może napotkać ten problem: jeśli Twój system ma harmonogram wątków, nie gwarantuje to, że jakikolwiek wątek, bez względu na to, jak niski będzie jego priorytet, w końcu dostanie szansę na uruchomienie, wtedy spinlocki mogą doprowadzić do trwałego impasu. Harmonogram iOS rozróżnia różne klasy wątków, a wątki w niższej klasie będą działały tylko wtedy, gdy żaden wątek w wyższej klasie również nie będzie chciał działać. Nie ma takiej strategii wycofywania się, więc jeśli masz stale dostępne wysokiej klasy wątki, wątki niskiej klasy nigdy nie otrzymają czasu procesora, a zatem nigdy nie będą miały szans na wykonanie jakiejkolwiek pracy.

Problem pojawia się w następujący sposób: Twój kod otrzymuje blokadę w wątku o niskiej klasie prio i gdy znajduje się w środku tego zamka, przekroczono kwant czasu i wątek przestaje działać. Jedynym sposobem, w jaki można ponownie zwolnić tę blokadę, jest odzyskanie przez procesor tego wątku o niskiej klasie prio, ale nie jest to gwarantowane. Możesz mieć kilka wątków wysokiej klasy prio, które stale chcą się uruchamiać, a harmonogram zadań zawsze będzie nadawał im priorytet. Jeden z nich może przejechać przez blokadę i spróbować go zdobyć, co oczywiście nie jest możliwe, a system sprawi, że ustąpi. Problem polega na tym: wątek, który dał, jest natychmiast dostępny do ponownego uruchomienia! Mając wyższy prio niż wątek przytrzymujący zamek, wątek przytrzymujący zamek nie ma szans na uzyskanie czasu działania procesora.

Dlaczego ten problem nie występuje w przypadku muteksów? Gdy wątek o wysokim prio nie może uzyskać muteksu, nie poddaje się, może trochę się zakręcić, ale ostatecznie zostanie wysłany do snu. Śpiąca nić nie jest dostępna do uruchomienia, dopóki nie zostanie obudzona przez zdarzenie, np. Zdarzenie takie jak odblokowany muteks, na który czekał. Apple zdaje sobie sprawę z tego problemu i dlatego przestało działać OSSpinLock. Nowa blokada nazywa się os_unfair_lock. Ta blokada pozwala uniknąć wyżej wspomnianej sytuacji, ponieważ jest świadoma różnych klas priorytetów wątków. Jeśli masz pewność, że używanie spinlocków jest dobrym pomysłem w projekcie iOS, skorzystaj z niego. Trzymać się z dala odOSSpinLock! I w żadnym wypadku nie implementuj własnych blokad w iOS! W razie wątpliwości użyj muteksu! Ten problem nie dotyczy systemu macOS, ponieważ ma on inny program do planowania wątków, który nie pozwala, aby żaden wątek (nawet wątki o niskim priorytecie) „działał na sucho” w czasie procesora, nadal może się tam pojawić taka sama sytuacja, a następnie prowadzić do bardzo słabej wydajność, dlatego OSSpinLockjest również przestarzała w systemie macOS.

Mecki
źródło
3
doskonałe wyjaśnienie ... Mam wątpliwości dotyczące blokady, czy mogę użyć blokady w ISR? jeśli nie, dlaczego nie
haris
4
@ Mecki Jeśli się nie mylę, uważam, że zasugerowałeś w swojej odpowiedzi, że podział czasu występuje tylko w systemach jednoprocesorowych. To nie jest poprawne! Możesz użyć blokady spinowej w systemie jednoprocesorowym, która będzie się obracać, aż upłynie czas kwantowy. Następnie może przejąć inny wątek o tym samym priorytecie (dokładnie tak, jak opisano dla systemów wieloprocesorowych).
fumoboy007
7
@ fumoboy007 "i będzie się obracać, dopóki nie skończy się jego kwant" // Co oznacza, że ​​marnujesz czas procesora / moc baterii na absolutnie nic, bez żadnej korzyści, co jest całkowicie moralne. I nie, nigdzie nie powiedziałem, że podział czasu występuje tylko w systemach z jednym rdzeniem, powiedziałem, że w systemach z jednym rdzeniem istnieje TYLKO podział czasu, podczas gdy istnieje PRAWDZIWA równoległość w systemach wielordzeniowych (a także podział czasu, ale nie ma znaczenia dla tego, co napisałem w moim Odpowiadać); również całkowicie nie rozumiesz, czym jest hybrydowy spinlock i dlaczego działa dobrze w systemach jedno- i wielordzeniowych.
Mecki,
11
@ fumoboy007 Wątek A utrzymuje blokadę i zostaje przerwany. Wątek B działa i chce blokady, ale nie może jej uzyskać, więc się obraca. W systemie wielordzeniowym Wątek A może nadal działać na innym rdzeniu, podczas gdy Wątek B wciąż się obraca, zwolnić blokadę, a Wątek B może kontynuować w swoim bieżącym kwantowym czasie. W systemie z jednym rdzeniem istnieje tylko jeden rdzeń, który może uruchomić wątek A, aby zwolnić blokadę, a ten rdzeń jest zajęty przez przędzenie nici B. Nie ma więc możliwości, aby spinlock mógł zostać kiedykolwiek zwolniony, zanim Nić B przekroczył swój kwant czasowy, a zatem całe wirowanie to tylko strata czasu.
Mecki
2
Jeśli chcesz dowiedzieć się więcej o blokadach i muteksach zaimplementowanych w jądrze Linuksa, gorąco polecam przeczytanie rozdziału 5 świetnych sterowników urządzeń Linux, wydanie trzecie (LDD3) (muteksy: strona 109; spinlocki: strona 116).
patryk.beza
7

Kontynuując sugestią Mecki, ten artykuł pthread mutex vs pthread spinlock na bloga Alexandra Sandlera, Alex na pokazach Linux, jak spinlocki mutexesmogą być realizowane w celu przetestowania zachowania pomocą #ifdef.

Pamiętaj jednak, aby podjąć ostateczne wezwanie w oparciu o Twoją obserwację, rozumiejąc, że podany przykład jest odosobnionym przypadkiem, wymagania projektowe, środowisko może być zupełnie inne.

TheCottonSilk
źródło
6

Należy również pamiętać, że w niektórych środowiskach i warunkach (takich jak uruchamianie w systemie Windows na poziomie wysyłki> = POZIOM WYSYŁKI), nie można używać muteksu, lecz spinlock. Na Uniksie - to samo.

Oto równoważne pytanie na stronie Unix stosu konkurenta: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- więcej

Informacje na temat wysyłki w systemach Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc

Dan Jobs
źródło
6

Odpowiedź Meckiego całkiem nieźle ją ukazuje. Jednak w przypadku pojedynczego procesora użycie blokady może mieć sens, gdy zadanie oczekuje na blokadę, która zostanie wydana przez procedurę obsługi przerwania. Przerwanie przenosi kontrolę do ISR, który przygotowuje zasób do użycia przez zadanie oczekujące. Zakończyłoby się to zwolnieniem blokady przed przywróceniem kontroli nad przerwanym zadaniem. Zadanie wirowania sprawi, że blokada będzie dostępna i będzie kontynuowana.

AlanC
źródło
2
Nie jestem pewien, czy w pełni zgodzę się z tą odpowiedzią. Jeden procesor, jeśli zadanie blokuje zasób, oznacza to, że ISR nie może bezpiecznie kontynuować i nie może czekać, aż zadanie odblokuje zasób (ponieważ zadanie trzymające zasób zostało przerwane). W takich przypadkach zadanie powinno po prostu wyłączyć przerwania, aby wymusić wykluczenie między sobą a ISR. Oczywiście trzeba to zrobić w bardzo krótkich odstępach czasu.
user1202136
3

Mechanizmy synchronizacji Spinlock i Mutex są dziś bardzo powszechne.

Najpierw pomyślmy o Spinlocku.

Zasadniczo jest to zajęta operacja oczekiwania, co oznacza, że ​​musimy poczekać na zwolnienie określonej blokady, zanim będziemy mogli przejść do następnej akcji. Koncepcyjnie bardzo proste, ale jego wdrożenie nie jest przypadkiem. Na przykład: jeśli blokada nie została zwolniona, to wątek został zamieniony i przejdzie w stan uśpienia, czy powinniśmy sobie z tym poradzić? Jak radzić sobie z blokadami synchronizacji, gdy dwa wątki jednocześnie żądają dostępu?

Zasadniczo najbardziej intuicyjnym pomysłem jest synchronizacja za pomocą zmiennej w celu ochrony sekcji krytycznej. Koncepcja Mutex jest podobna, ale wciąż są różne. Nacisk na: Wykorzystanie procesora. Spinlock zużywa czas procesora na wykonanie czynności, dlatego też możemy podsumować różnicę między tymi dwoma:

W homogenicznych środowiskach wielordzeniowych, jeśli czas spędzony na sekcji krytycznej jest niewielki, użyj Spinlocka, ponieważ możemy skrócić czas przełączania kontekstu. (Porównanie pojedynczego rdzenia nie jest ważne, ponieważ niektóre implementacje systemów Spinlock w środku przełącznika)

W systemie Windows użycie Spinlock spowoduje uaktualnienie wątku do DISPATCH_LEVEL, co w niektórych przypadkach może być niedozwolone, więc tym razem musieliśmy użyć Mutex (APC_LEVEL).

Marcus Thornton
źródło
-6

Używanie spinlocków w systemie jednordzeniowym / jednoprocesorowym zwykle nie ma sensu, ponieważ dopóki odpytywanie spinlocka blokuje jedyny dostępny rdzeń procesora, żaden inny wątek nie może działać, a ponieważ żaden inny wątek nie może działać, blokada nie będzie działać bądź odblokowany. IOW, spinlock marnuje tylko czas procesora na tych systemach, bez realnych korzyści

To jest źle. Korzystanie z blokad spinowych w systemach z procesorem uni nie powoduje marnowania cykli procesora, ponieważ gdy proces przejdzie w blokadę spinania, zapobieganie jest wyłączone, dlatego nie może być nikogo innego! Po prostu korzystanie z niego nie ma żadnego sensu! Stąd blokady w systemach Uni są zastępowane przez preempt_disable w czasie kompilacji przez jądro!

Neelansh Mittal
źródło
Cytowany jest nadal całkowicie prawdziwy. Jeśli skompilowany wynik kodu źródłowego nie zawiera blokad spinowych, wówczas cytowany tekst jest nieistotny. Zakładając, że to, co powiedziałeś, jest prawdą o jądrze zastępującym blokady blokujące w czasie kompilacji, w jaki sposób obsługiwane są blokady blokujące podczas prekompilacji na innej maszynie, która może, ale nie musi być uniprocesorem, chyba że mówimy ściśle o blokadach blokujących tylko w samym jądrze?
Hydranix,
„Gdy proces przejdzie w tryb blokady wirowania, zapobieganie jest wyłączone”. Zapobieganie nie jest wyłączone, gdy proces zaczyna się kręcić. Gdyby tak było, jeden proces mógłby zniszczyć całą maszynę, po prostu wprowadzając blokadę i nigdy nie wychodząc. Zauważ, że jeśli twój wątek działa w przestrzeni jądra (zamiast przestrzeni użytkownika), zastosowanie blokady spinowej rzeczywiście wyłącza zapobieganie, ale nie sądzę, że o tym tutaj dyskutujemy.
Konstantin Weitz
W czasie kompilacji przez jądro ?
Shien
Blokada spinowa FYI @konstantin może być podjęta tylko w przestrzeni jądra. A po przyjęciu blokady wirowania na lokalnym procesorze wyłączane jest zapobieganie.
Neelansh Mittal
@hydranix Nie udało ci się? Oczywiście nie możesz skompilować modułu dla jądra, w którym jest włączony CONFIG_SMP, i uruchomić ten sam moduł na jądrze, dla którego CONFIG_SMP jest wyłączony.
Neelansh Mittal