Myślę, że obaj wykonują tę samą pracę, jak zdecydować, którego użyć do synchronizacji?
Myślę, że obaj wykonują tę samą pracę, jak zdecydować, którego użyć do synchronizacji?
Odpowiedzi:
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,
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 OSSpinLock
jest również przestarzała w systemie macOS.
Kontynuując sugestią Mecki, ten artykuł pthread mutex vs pthread spinlock na bloga Alexandra Sandlera, Alex na pokazach Linux, jak spinlock
i mutexes
mogą 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.
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
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.
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).
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!