Staram się w pełni zrozumieć kolejki równoległe i szeregowe w GCD. Mam pewne problemy i mam nadzieję, że ktoś może mi odpowiedzieć jasno i na temat.
Czytam, że kolejki szeregowe są tworzone i używane do wykonywania zadań jedna po drugiej. Jednak co się stanie, jeśli:
- Tworzę kolejkę szeregową
- Używam
dispatch_async
(w właśnie utworzonej kolejce szeregowej) trzy razy, aby wysłać trzy bloki A, B, C
Czy trzy bloki zostaną wykonane:
w kolejności A, B, C, ponieważ kolejka jest szeregowa
LUB
- współbieżnie (w tym samym czasie na równoległych wątkach), ponieważ użyłem wysyłki ASYNC
Czytam, że mogę używać
dispatch_sync
na współbieżnych kolejkach, aby wykonywać bloki jeden po drugim. W takim przypadku DLACZEGO w ogóle istnieją kolejki szeregowe, skoro zawsze mogę korzystać z kolejki współbieżnej, w której mogę wysyłać SYNCHRONICZNIE tyle bloków, ile chcę?Dzięki za dobre wyjaśnienie!
ios
multithreading
concurrency
grand-central-dispatch
Bogdan Alexandru
źródło
źródło
Odpowiedzi:
Prosty przykład: masz blok, którego wykonanie zajmuje minutę. Dodajesz go do kolejki z głównego wątku. Spójrzmy na cztery przypadki.
Oczywiście nie używałbyś żadnego z dwóch ostatnich do długotrwałych procesów. Zwykle widzisz to, gdy próbujesz zaktualizować interfejs użytkownika (zawsze w głównym wątku) z czegoś, co może być uruchomione w innym wątku.
źródło
Oto kilka eksperymentów, które przeprowadziłem, aby zrozumieć
serial
, czym sąconcurrent
kolejkiGrand Central Dispatch
.Oto podsumowanie tych eksperymentów
Pamiętaj, że korzystając z GCD, dodajesz zadanie tylko do kolejki i wykonujesz zadanie z tej kolejki. Kolejka wysyła Twoje zadanie w wątku głównym lub w tle, w zależności od tego, czy operacja jest synchroniczna czy asynchroniczna. Typy kolejek to Serial, Concurrent, Main dispatch queue.Wszystkie wykonywane zadania są wykonywane domyślnie z głównej kolejki wysyłkowej.Istnieją już cztery predefiniowane globalne kolejki współbieżne, z których może korzystać Twoja aplikacja i jedna główna kolejka (DispatchQueue.main). można również ręcznie utworzyć własną kolejkę i wykonać zadanie z tej kolejki.
Zadanie związane z interfejsem użytkownika powinno być zawsze wykonywane z głównego wątku poprzez wysłanie zadania do kolejki głównej
DispatchQueue.main.sync/async
podczas gdy operacje związane z siecią / ciężkie operacje powinny być zawsze wykonywane asynchronicznie, bez względu na to, w jakim wątku używasz głównego lub tłaEDYCJA: Jednak są przypadki, w których musisz wykonywać operacje wywołań sieciowych synchronicznie w wątku w tle bez zamrażania interfejsu użytkownika (np. Odświeżanie tokena OAuth i czekanie, czy się powiedzie, czy nie) .Musisz opakować tę metodę wewnątrz operacji asynchronicznej. operacje są wykonywane w kolejności i bez blokowania głównego wątku.
EDIT EDIT: Można oglądać filmy demo tutaj
źródło
}
ponieważ tak naprawdę nie jest wykonywany w tym momencieconcurrentQueue.sync
ofdoLongSyncTaskInConcurrentQueue()
, wyświetla główny wątek,Task will run in different thread
wydaje się nieprawda.Po pierwsze, ważne jest, aby znać różnicę między wątkami i kolejkami oraz tym, co naprawdę robi GCD. Kiedy używamy kolejek wysyłkowych (poprzez GCD), tak naprawdę robimy kolejkowanie, a nie wątkowanie. Framework Dispatch został zaprojektowany specjalnie po to, aby odciągnąć nas od wątków, ponieważ Apple przyznaje, że „wdrożenie prawidłowego rozwiązania wątkowego [może] stać się niezwykle trudne, jeśli nie [czasami] niemożliwe do osiągnięcia”. Dlatego, aby wykonywać zadania jednocześnie (zadania, których nie chcemy zamrażać interfejsu użytkownika), wystarczy utworzyć kolejkę tych zadań i przekazać ją do GCD. GCD obsługuje wszystkie powiązane wątki. Dlatego tak naprawdę wszystko, co robimy, to kolejkowanie.
Drugą rzeczą, którą należy od razu wiedzieć, jest to, czym jest zadanie. Zadanie to cały kod w tym bloku kolejki (nie w kolejce, ponieważ możemy dodawać rzeczy do kolejki przez cały czas, ale w ramach zamknięcia, w którym dodaliśmy je do kolejki). Zadanie jest czasami określane jako blok, a blok jest czasami określany jako zadanie (ale są one częściej nazywane zadaniami, szczególnie w społeczności Swift). Bez względu na to, ile lub mało kodu, cały kod w nawiasach klamrowych jest traktowany jako jedno zadanie:
I jest oczywiste wspomnieć, że współbieżność oznacza po prostu w tym samym czasie z innymi rzeczami, a serial oznacza jeden po drugim (nigdy w tym samym czasie). Serializowanie czegoś lub umieszczanie czegoś w serialu oznacza po prostu wykonywanie tego od początku do końca w kolejności od lewej do prawej, od góry do dołu, nieprzerwanie.
Istnieją dwa typy kolejek, szeregowe i współbieżne, ale wszystkie kolejki są współbieżne względem siebie . Fakt, że chcesz uruchamiać dowolny kod „w tle” oznacza, że chcesz go uruchamiać jednocześnie z innym wątkiem (zwykle głównym wątkiem). Dlatego wszystkie kolejki wysyłania, seryjne lub współbieżne, wykonują swoje zadania współbieżnie względem innych kolejek . Każda serializacja wykonywana przez kolejki (przez kolejki szeregowe) dotyczy tylko zadań w ramach tej pojedynczej [seryjnej] kolejki wysyłkowej (tak jak w powyższym przykładzie, w którym istnieją dwa zadania w tej samej kolejce szeregowej; te zadania będą wykonywane jedno po drugi, nigdy jednocześnie).
KOLEJKI SZEREGOWE (często nazywane prywatnymi kolejkami wysyłkowymi) gwarantują wykonanie zadań pojedynczo od początku do końca w kolejności, w jakiej zostały dodane do tej konkretnej kolejki. Jest to jedyna gwarancja serializacji w każdym miejscu w omówieniu kolejek wysyłki - że określone zadania w ramach określonej kolejki szeregowej są wykonywane szeregowo. Kolejki szeregowe mogą jednak działać jednocześnie z innymi kolejkami szeregowymi, jeśli są oddzielnymi kolejkami, ponieważ znowu wszystkie kolejki są względem siebie współbieżne. Wszystkie zadania są uruchamiane w różnych wątkach, ale nie gwarantuje się, że każde zadanie zostanie uruchomione w tym samym wątku (nie jest to ważne, ale warto wiedzieć). A framework iOS nie zawiera żadnych gotowych do użycia kolejek szeregowych, musisz je utworzyć. Prywatne (nieglobalne) kolejki są domyślnie szeregowe, więc aby utworzyć kolejkę szeregową:
Możesz uczynić go współbieżnym poprzez jego właściwość atrybutu:
Ale w tym momencie, jeśli nie dodajesz żadnych innych atrybutów do kolejki prywatnej, Apple zaleca użycie jednej z ich gotowych do użycia globalnych kolejek (wszystkie są współbieżne). U dołu tej odpowiedzi zobaczysz inny sposób tworzenia kolejek szeregowych (przy użyciu właściwości target), zgodnie z zaleceniami Apple (w celu wydajniejszego zarządzania zasobami). Ale na razie wystarczy oznakowanie.
CONCURRENT QUEUES (często nazywane globalnymi kolejkami wysyłania) mogą wykonywać zadania jednocześnie; zadania mają jednak zagwarantować , że zostaną zainicjowane w kolejności, w jakiej zostały dodane do tej konkretnej kolejki, ale w przeciwieństwie do kolejek szeregowych kolejka nie czeka na zakończenie pierwszego zadania przed rozpoczęciem drugiego zadania. Zadania (podobnie jak kolejki szeregowe) są uruchamiane w odrębnych wątkach i (podobnie jak w przypadku kolejek szeregowych) nie gwarantuje się, że każde zadanie zostanie uruchomione w tym samym wątku (nie jest to ważne, ale warto wiedzieć). Struktura iOS zawiera cztery gotowe do użycia współbieżne kolejki. Możesz utworzyć kolejkę współbieżną, korzystając z powyższego przykładu lub używając jednej z globalnych kolejek Apple (co jest zwykle zalecane):
Istnieją dwa sposoby wysyłania kolejek: synchronicznie i asynchronicznie.
SYNC DISPATCHING oznacza, że wątek, do którego została wysłana kolejka (wątek wywołujący), zatrzymuje się po wysłaniu kolejki i czeka na zakończenie wykonywania zadania w tym bloku kolejki przed wznowieniem. Aby wysłać synchronicznie:
ASYNC DISPATCHING oznacza, że wątek wywołujący kontynuuje działanie po wysłaniu kolejki i nie czeka na zakończenie wykonywania zadania w tym bloku kolejki. Aby wysłać asynchronicznie:
Teraz można by pomyśleć, że aby wykonać zadanie szeregowo, należy użyć kolejki szeregowej, a to nie do końca prawda. Aby wykonać wiele zadań szeregowo, należy użyć kolejki szeregowej, ale wszystkie zadania (izolowane same) są wykonywane szeregowo. Rozważmy ten przykład:
Bez względu na to, jak skonfigurujesz (szeregowo lub współbieżnie) lub wysyłasz (synchronizuj lub asynchronicznie) tę kolejkę, to zadanie zawsze będzie wykonywane szeregowo. Trzecia pętla nigdy nie będzie działać przed drugą pętlą, a druga pętla nigdy nie będzie działać przed pierwszą pętlą. Dotyczy to każdej kolejki korzystającej z dowolnej wysyłki. Dzieje się tak, gdy wprowadzasz wiele zadań i / lub kolejek, gdzie serial i współbieżność naprawdę wchodzą w grę.
Rozważ te dwie kolejki, jedną szeregową i jedną równoległą:
Powiedzmy, że wysyłamy dwie współbieżne kolejki w trybie asynchronicznym:
Ich dane wyjściowe są pomieszane (zgodnie z oczekiwaniami), ale zauważ, że każda kolejka wykonywała swoje własne zadanie szeregowo. To najbardziej podstawowy przykład współbieżności - dwa zadania działające jednocześnie w tle w tej samej kolejce. Teraz zróbmy pierwszy serial:
Czy pierwsza kolejka nie powinna być wykonywana szeregowo? Było (i było drugie). Cokolwiek wydarzyło się w tle, nie ma znaczenia dla kolejki. Powiedzieliśmy kolejce szeregowej, aby wykonywała szeregowo i tak się stało ... ale daliśmy jej tylko jedno zadanie. Teraz dajmy mu dwa zadania:
I to jest najbardziej podstawowy (i jedyny możliwy) przykład serializacji - dwa zadania działające szeregowo (jedno po drugim) w tle (do głównego wątku) w tej samej kolejce. Ale jeśli utworzyliśmy dla nich dwie oddzielne kolejki szeregowe (ponieważ w powyższym przykładzie są to ta sama kolejka), ich dane wyjściowe są ponownie pomieszane:
I to właśnie miałem na myśli, mówiąc, że wszystkie kolejki są względem siebie współbieżne. Są to dwie kolejki szeregowe wykonujące swoje zadania w tym samym czasie (ponieważ są to oddzielne kolejki). Kolejka nie zna innych kolejek lub ich nie obchodzi. Wróćmy teraz do dwóch kolejek szeregowych (z tej samej kolejki) i dodajmy trzecią kolejkę, współbieżną:
To trochę nieoczekiwane, dlaczego kolejka współbieżna czekała na zakończenie kolejki szeregowej, zanim została wykonana? To nie jest współbieżność. Twój plac zabaw może pokazywać inny wynik, ale mój pokazał to. Pokazało to, ponieważ priorytet mojej kolejki współbieżnej nie był wystarczająco wysoki, aby GCD mógł wykonać swoje zadanie wcześniej. Więc jeśli zachowam wszystko bez zmian, ale zmienię QoS globalnej kolejki (jej jakość usług, która jest po prostu poziomem priorytetu kolejki)
let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
, wynik będzie zgodny z oczekiwaniami:Dwie kolejki szeregowe wykonywały swoje zadania szeregowo (zgodnie z oczekiwaniami), a kolejka współbieżna wykonywała swoje zadanie szybciej, ponieważ otrzymała wysoki priorytet (wysoki QoS lub jakość usług).
Dwie równoległe kolejki, tak jak w naszym pierwszym przykładzie drukowania, pokazują pomieszany wydruk (zgodnie z oczekiwaniami). Aby sprawić, że będą drukować porządnie w trybie szeregowym, musielibyśmy utworzyć obie z tej samej kolejki szeregowej (również ta sama instancja tej kolejki, a nie tylko ta sama etykieta) . Następnie każde zadanie jest wykonywane szeregowo względem drugiego. Jednak innym sposobem, aby zmusić je do drukowania seryjnego, jest zachowanie ich obu jednocześnie, ale zmiana ich metody wysyłania:
Pamiętaj, że wysyłanie synchronizacji oznacza tylko, że wątek wywołujący czeka na zakończenie zadania w kolejce przed kontynuowaniem. Zastrzeżenie w tym miejscu polega oczywiście na tym, że wątek wywołujący jest zamrożony do czasu zakończenia pierwszego zadania, co może, ale nie musi, być zgodne z oczekiwaniami interfejsu użytkownika.
Z tego powodu nie możemy wykonać następujących czynności:
Jest to jedyna możliwa kombinacja kolejek i metod wysyłania, której nie możemy wykonać - wysyłanie synchroniczne na głównej kolejce. A to dlatego, że prosimy główną kolejkę o zatrzymanie się, dopóki nie wykonamy zadania w nawiasach klamrowych ... które wysłaliśmy do głównej kolejki, którą właśnie zamroziliśmy. Nazywa się to impasem. Aby zobaczyć to w akcji na placu zabaw:
Ostatnią rzeczą, o której należy wspomnieć, są zasoby. Kiedy przydzielamy kolejce zadanie, GCD znajduje dostępną kolejkę z jej wewnętrznie zarządzanej puli. Jeśli chodzi o pisanie tej odpowiedzi, na QoS są dostępne 64 kolejki. Może się wydawać, że to dużo, ale można je szybko skonsumować, zwłaszcza przez biblioteki innych firm, w szczególności struktury baz danych. Z tego powodu Apple ma zalecenia dotyczące zarządzania kolejkami (wymienione w poniższych linkach); jedna istota:
Aby to zrobić, zamiast tworzyć je tak, jak robiliśmy to wcześniej (co nadal możesz), Apple zaleca tworzenie takich kolejek szeregowych:
Do dalszej lektury polecam:
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue
źródło
Jeśli dobrze rozumiem, jak działa GCD, myślę, że są dwa rodzaje
DispatchQueue
,serial
aconcurrent
jednocześnie są dwa sposobyDispatchQueue
wysyłania jego zadań, przypisanyclosure
, pierwszyasync
i drugisync
. Te razem określają, w jaki sposób faktycznie wykonywane jest zamknięcie (zadanie).Znalazłem to
serial
i mam naconcurrent
myśli, ile wątków może wykorzystać kolejka,serial
oznacza jeden, aconcurrent
oznacza wiele. Async
iasync
oznacza to, że zadanie zostanie wykonane na który wątek, wątek rozmówcy lub gwint dotycząca tego kolejkę,sync
środki uruchamiane na wątku wywołującego natomiastasync
środki uruchamiane na bazowym wątku.Poniżej znajduje się kod eksperymentalny, który można uruchomić na placu zabaw Xcode.
Mam nadzieję, że to może być pomocne.
źródło
Lubię to myśleć, używając tej metafory (tutaj jest link do oryginalnego obrazu):
Wyobraźmy sobie, że twój tata zmywa naczynia, a ty właśnie wypiłeś szklankę sody. Przynosisz szklankę tacie, żeby ją wyczyścił, i stawiasz ją obok drugiego naczynia.
Teraz twój tata zmywa naczynia sam, więc będzie musiał zmywać je jeden po drugim: Twój tata reprezentuje kolejkę szeregową .
Ale tak naprawdę nie jesteś zainteresowany staniem tam i patrzeniem, jak jest sprzątany. Więc upuszczasz szklankę i wracasz do swojego pokoju: nazywa się to wysyłaniem asynchronicznym . Twój tata może, ale nie musi, dać ci znać, kiedy skończy, ale ważne jest to, że nie czekasz, aż szkło zostanie wyczyszczone; wracasz do swojego pokoju, żeby robić, wiesz, dziecięce rzeczy.
Teraz załóżmy, że nadal jesteś spragniony i chcesz napić się wody na tej samej szklance, która jest twoją ulubioną, i naprawdę chcesz ją odzyskać, gdy tylko zostanie wyczyszczona. Więc stoisz tam i patrzysz, jak twój tata zmywa naczynia, dopóki twoje nie skończy. To jest wysłanie synchronizacji , ponieważ jesteś zablokowany, gdy czekasz na zakończenie zadania.
Na koniec powiedzmy, że twoja mama postanawia pomóc tacie i dołącza do niego zmywaniem. Teraz kolejka staje się kolejką współbieżną, ponieważ mogą jednocześnie czyścić wiele naczyń; ale pamiętaj, że nadal możesz zdecydować się tam poczekać lub wrócić do swojego pokoju, niezależnie od tego, jak działają.
Mam nadzieję że to pomoże
źródło
1. Czytam, że kolejki szeregowe są tworzone i wykorzystywane do wykonywania zadań jedna po drugiej. Jednak co się stanie, jeśli: - • utworzę kolejkę szeregową • użyję dispatch_async (na właśnie utworzonej kolejce szeregowej) trzy razy do wysłania trzech bloków A, B, C
ODPOWIEDŹ : - Wszystkie trzy bloki wykonywane jeden po drugim Stworzyłem jeden przykładowy kod, który pomaga zrozumieć.
źródło