Ahoj! Jmenuji se Kiryl Famin a jsem vývojář pro iOS.
V tomto článku pokryjeme Grand Central Dispatch (GCD) jednou provždy. Ačkoli se GCD může nyní, když existuje Swift Modern Concurrency, zdát zastaralé, kód využívající tento rámec se bude objevovat ještě mnoho let – jak v produkci, tak v rozhovorech.
Dnes se zaměříme pouze na základní porozumění GCD. Podrobně prozkoumáme pouze klíčové aspekty multithreadingu, včetně vztahu mezi frontami a vlákny — téma, které mnoho jiných článků obvykle přehlíží. S těmito koncepty pro vás bude snazší porozumět tématům, jako je DispatchGroup
, DispatchBarrier
, semafor, mutex a tak dále.
Tento článek bude užitečný pro začátečníky i zkušené vývojáře. Pokusím se vše vysvětlit jasným jazykem a vyhnout se přetížení odbornými termíny.
Vlákno – v podstatě kontejner, kde je umístěna a vykonávána sada systémových instrukcí. Ve skutečnosti veškerý spustitelný kód běží na nějakém vláknu. Rozlišujeme hlavní vlákno a pracovní vlákna.
Multithreading – schopnost systému spouštět několik vláken současně (současně). To umožňuje paralelní běh více větví kódu.
Grand Central Dispatch (GCD) – framework, který usnadňuje práci s vlákny (využívá výhody multithreadingu). Jeho hlavní primitiva jsou úkoly a fronty.
GCD je tedy nástroj, který usnadňuje psaní kódu, který se spouští souběžně. Jednoduchým příkladem je přesunutí náročných výpočtů do samostatného vlákna, aby nedošlo k narušení aktualizací uživatelského rozhraní v hlavním vlákně.
Úkol – sada pokynů seskupených vývojářem. Je důležité pochopit, že vývojář rozhoduje, který kód patří ke konkrétní úloze.
Například:
print(“GCD”) // a task
let database = Database() let person = Person(age: 23) // also a task database.store(person)
Fronta – základní primitiv GCD, je to místo, kam vývojář zadává úkoly k provedení. Fronta přebírá odpovědnost za distribuci úloh mezi vlákna (každá fronta má přístup k systémové oblasti vláken).
Fronty vám v podstatě umožňují soustředit se na uspořádání kódu do úloh, spíše než přímou správu vláken. Když odešlete úlohu do fronty, bude provedena v dostupném vláknu – často odlišném od toho, které bylo použito k odeslání úlohy.
Můžete najít mp4 verze všech GIFů
Hlavní fronta – fronta, která se spouští pouze v hlavním vláknu. Je sériový (o tom později).
let mainQueue = DispatchQueue.main
Globální fronty – systém poskytuje 5 front (jedna pro každou úroveň priority). Jsou souběžné.
let globalQueue = DispatchQueue.global()
Vlastní fronty – fronty vytvořené vývojářem. Vývojář si vybere jednu z 5 priorit a typ: sériový nebo souběžný (ve výchozím nastavení jsou sériové).
let userQueue = DispatchQueue(label: “com.kirylfamin.concurrent”, attributes: .concurrent).
Quality of Service (QoS) – systém pro priority fronty. Čím vyšší prioritu má fronta, ve které je úkol zařazen, tím více zdrojů je pro něj alokováno. K dispozici je celkem 5 úrovní QoS:
.userInteractive
– nejvyšší priorita. Používá se pro úlohy, které vyžadují okamžité provedení, ale nejsou vhodné pro spuštění v hlavním vláknu. Například v aplikaci, která umožňuje retušování obrazu v reálném čase, musí být výsledek retušování vypočten okamžitě; pokud by se to však dělalo v hlavním vláknu, narušovalo by to aktualizace uživatelského rozhraní a ovládání gest, ke kterým dochází vždy v hlavním vláknu (např. když uživatel přejede prstem po oblasti, která má být retušována, a aplikace musí okamžitě zobrazit výsledek „pod prstem“). Výsledek tak získáme co nejrychleji, aniž bychom zatížili hlavní vlákno.
.userInitiated
– priorita pro úkoly, které vyžadují rychlou zpětnou vazbu, i když nejsou tak důležité jako interaktivní úkoly. Obvykle se používá pro úlohy, kdy uživatel chápe, že úloha nebude dokončena okamžitě a bude muset čekat (například požadavek serveru).
.default
– standardní priorita. Přiřadí se, pokud vývojář při vytváření fronty nespecifikuje QoS – když pro úlohu neexistují žádné specifické požadavky a její prioritu nelze určit z kontextu (například pokud vyvoláte úlohu z fronty s prioritou .userInitiated, úloha tuto prioritu zdědí).
.utility
– priorita pro úkoly, které nevyžadují okamžitou zpětnou vazbu od uživatele, ale jsou nezbytné pro fungování aplikace. Například synchronizace dat se serverem nebo zápis automatického ukládání na disk.
.background
– nejnižší priorita. Příkladem je čištění mezipaměti.
Všechny fronty jsou klasifikovány jako sériové fronty nebo souběžné fronty
Sériové fronty – jak název napovídá, jedná se o fronty, kde se úlohy provádějí jedna po druhé. To znamená, že další úloha začíná až po dokončení aktuálního .
Souběžné fronty – Tyto fronty umožňují provádění úloh paralelně – nová úloha se spustí, jakmile jsou přiděleny prostředky, bez ohledu na to, zda byly dokončeny předchozí úlohy. Pamatujte, že je zaručeno pouze pořadí zahájení (úloha zařazená dříve bude zahájena před pozdější), ale pořadí dokončení není zaručeno.
Je důležité poznamenat, že nyní diskutujeme o metodách provádění úloh ve vztahu k volajícímu vláknu . Jinými slovy, způsob, jakým voláte úlohu, určuje, jak se události vyvinou ve vláknu, ze kterého odešlete úlohu do fronty.
async
)Asynchronní volání je takové, kde volající vlákno není blokováno – to znamená, že nečeká na provedení úlohy, kterou zařadí do fronty.
DispatchQueue.main.async { print(“A”) } print(“B”)
V tomto příkladu asynchronně zařazujeme úlohu do fronty k print("A")
v hlavní frontě z hlavního vlákna (protože tento kód není v žádné konkrétní frontě, je standardně spuštěn v hlavním vláknu). Nečekáme tedy na úlohu v hlavním vláknu a ihned pokračujeme v provádění. V tomto konkrétním příkladu je úloha print("A")
zařazena do fronty v hlavní frontě a poté je v hlavním vlákně okamžitě spuštěna print("B")
. Protože hlavní vlákno je zaneprázdněno prováděním aktuálního kódu (a úlohy z hlavní fronty se mohou provádět pouze v hlavním vlákně), aktuální úloha print("B")
skončí jako první a teprve poté, co je hlavní vlákno volné, se spustí úloha print("A")
zařazená do fronty v hlavní frontě . Výstup je: BA.
DispatchQueue.global().async { updateData() DispatchQueue.main.async { updateInterface() } Logger.log(.success) } indicateLoading()
Asynchronně přidáváme úlohu do globální fronty s výchozí prioritou z hlavního vlákna – takže volající vlákno okamžitě pokračuje a volá indicateLoading()
.
Po nějaké době systém alokuje prostředky pro úlohu a provede ji na volném pracovním vláknu z fondu vláken a zavolá se updateData()
.
Úloha obsahující updateInterface()
je zařazena asynchronně do hlavní fronty – volající pracovní vlákno nečeká na své dokončení a pokračuje.
Protože úkoly jsou zařazeny do fronty asynchronně, nemůžeme si být jisti, kdy budou zdroje přiděleny. V tomto případě nemůžeme s jistotou říci, zda se updateInterface()
(v hlavním vláknu) nebo Logger.log(.success)
(na pracovním vláknu) provede jako první (ani nemůžeme v krocích 1-2: který se spustí jako první, indicateLoading()
v hlavním vláknu nebo updateData()
v pracovním vláknu). I když je hlavní vlákno zaneprázdněné zpracováváním aktualizací uživatelského rozhraní, zpracováním gest a dalšími základními úkoly, vždy dostává maximum systémových prostředků. Na druhou stranu lze prostředky pro spuštění na pracovním vláknu alokovat také téměř okamžitě.
Všimněte si, že v této animaci globální fronta vykonává své úkoly na nějakém volném pracovním vláknu
sync
)Synchronní volání je takové, kde se volající vlákno zastaví a čeká na dokončení úlohy, kterou zařadilo do fronty.
let userQueue = DispatchQueue(label: "com.kirylfamin.serial") DispatchQueue.global().async { var account = BankAccount() userQueue.sync { account.balance += 10 } let balance = account.balance print(balance) }
Zde z pracovního vlákna provádějícího úkol v globální frontě synchronně zařazujeme úkol do vlastní fronty, abychom zvýšili zůstatek. Aktuální vlákno je zablokováno a čeká na dokončení úlohy zařazené do fronty. Zůstatek se tedy vytiskne až poté, co úkol ve vlastní frontě dokončí přírůstek.
Poznámka: Ve výše uvedené animaci provádí vlastní fronta své úkoly v některém volném pracovním vláknu
V kontextu synchronních úloh je důležité diskutovat o uváznutí – kdy vlákno nebo vlákna nekonečně dlouho čekají na sebe nebo na sebe, aby pokračovaly. Nejběžnějším příkladem je volání DispatchQueue.main.sync {} z hlavního vlákna .
Hlavní vlákno je zaneprázdněno prováděním aktuální úlohy, v rámci které chceme synchronně spustit nějaký kód. Synchronní volání tedy blokuje hlavní vlákno . Úloha je zařazena do fronty v hlavní frontě, ale nelze ji spustit, protože hlavní vlákno je zablokováno a čeká na dokončení aktuální úlohy – a úlohy v hlavní frontě lze spouštět pouze v hlavním vláknu . To může být zpočátku těžké si představit, ale klíčové je pochopit, že úloha zařazená do fronty s DispatchQueue.main.sync
se stává součástí aktuální úlohy a my ji řadíme do fronty za aktuální úlohou. Výsledkem je, že vlákno čeká na část aktuální úlohy, kterou nelze spustit, protože vlákno je obsazeno aktuální úlohou.
func printing() { print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Všimněte si, že print("B")
z hlavní fronty nelze provést, protože hlavní vlákno je blokováno.
V této části se všemi dosud získanými znalostmi probereme cvičení různé složitosti: od jednoduchých bloků kódu, se kterými se setkáte při pohovorech, až po pokročilé výzvy, které posouvají vaše chápání souběžného programování. Otázka ve všech těchto úlohách zní: Co bude vytištěno na konzoli?
Pamatujte, že hlavní fronta je sériová, fronty global() jsou souběžné a někdy může problém zahrnovat vlastní fronty se specifickými atributy.
Začneme úlohami normální obtížnosti – těmi s malou pravděpodobností nejistoty ve výstupu. Tyto úkoly se nejčastěji objevují v pohovorech; klíčem je věnovat čas a pečlivě analyzovat problém.
Úplný kód všech cvičení naleznete zde .
Úkol 1
print(“A”) DispatchQueue.main.async { print(“B”) } print(“C”)
print("A")
.print("B")
je zařazena asynchronně do hlavní fronty. Protože je hlavní vlákno zaneprázdněné, čeká tato úloha ve frontě.print("C")
.print("B")
.
Odpověď : ACB
Úkol 2
print(“A”) DispatchQueue.main.async { print(“B”) } DispatchQueue.main.async { print(“C”) } print(“D”)
print("A")
.print("B")
je zařazena do hlavní fronty. Hlavní fronta, dokud nebude k dispozici hlavní vlákno.print("C")
je zařazena do fronty po tisku("B") a také čeká.print("B")
.print("C")
.
Odpověď : ADBC
Hned bych měl zmínit, že v některých příkladech trochu zjednoduším vysvětlení a vynechám skutečnost, že systém optimalizuje provádění synchronních volání, o čemž se budeme bavit později.
Úkol 3
print(“A”) DispatchQueue.main.async { // 1 print(“B”) DispatchQueue.main.async { print(“C”) } DispatchQueue.global().sync { print(“D”) } DispatchQueue.main.sync { // 2 print(“E”) } } // 3 print(“F”) DispatchQueue.main.async { print(“G”) }
print("A")
se provede v hlavním vláknu."F"
.print("G")
je zařazena do fronty po předchozí úloze (kroky 1–3).print("B")
.print("C")
je pak zařazena do fronty v hlavní frontě (kde se aktuální úloha stále provádí a print("G")
ji ve frontě následuje). Vzhledem k tomu, že se přidává asynchronně, nečekáme na jeho provedení a jedeme hned dál.print("D")
zařazena do globální fronty. Protože je toto volání synchronní, počkáme, až jej provede globální fronta (může běžet na jakémkoli dostupném pracovním vláknu), než budeme pokračovat.print("E")
zařazena do fronty v hlavní frontě. Protože je toto volání synchronní, musí být aktuální vlákno zablokováno, dokud nebude úloha dokončena. V hlavní frontě jsou však již úkoly a operace print("E")
je přidána na konec, za nimi. Proto se tyto operace musí nejprve provést, než bude možné spustit print("E")
. Ale hlavní vlákno je stále zaneprázdněno prováděním aktuální operace, takže nemůže přejít na další operace ve frontě. I když po aktuální operaci nebyly provedeny žádné operace pro tisk "G"
a "C"
, vlákno stále nemohlo pokračovat, protože aktuální operace (kroky 1–3) ještě nebyla dokončena."G"
a "C"
.
Odpověď : AFBD
Alternativní odpověď (pokud bylo druhé volání async
): AFBDGCE
Úkol 4
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”) serialQueue.async { // 1 print(“A”) serialQueue.sync { print(“B”) } print(“C”) } // 2
Úloha (kroky 1–2) je zařazena asynchronně do vlastní sériové fronty (ve výchozím nastavení jsou fronty sériové, protože jsme nepoužili atribut .concurrent
).
"A"
.print("B")
. Protože je volání synchronní, vlákno blokuje čekání na své provedení.print("B")
spustit, což má za následek uváznutí.
Odpověď : A, uváznutí
Tento příklad ukazuje, že k uváznutí může dojít v jakékoli sériové frontě – ať už jde o hlavní frontu nebo vlastní frontu.
Úkol 5
Vyměňme sériovou frontu z předchozí úlohy za souběžnou.
DispatchQueue.global().async { // 1 print("A") DispatchQueue.global().sync { print("B") } print("C") } // 2
"A"
.print("B")
ve stejné globální frontě, které zablokuje aktuální pracovní vlákno , dokud nebude úloha dokončena.print("B")
v jiném pracovním vláknu."C"
.Odpověď : ABC
Úkol 6
print("A") DispatchQueue.main.async { // 1 print("B") DispatchQueue.main.async { // 2 print("C") DispatchQueue.main.async { // 3 print("D") DispatchQueue.main.sync { print("E") } } // 4 } // 5 DispatchQueue.global().sync { // 6 print("F") DispatchQueue.global().sync { print("G") } } // 7 print("H") } // 8 print("I")
Hlavní vlákno tiskne "A"
.
Asynchronní úloha (kroky 1–8) je zařazena do fronty v hlavní frontě bez blokování aktuálního vlákna.
Hlavní vlákno pokračuje a tiskne "I"
.
Později, když je hlavní vlákno volné, úloha zařazená do hlavní fronty zahájí provádění a vytiskne "B"
.
Další asynchronní úloha (kroky 2–5) je zařazena do fronty v hlavní frontě – neblokuje aktuální vlákno.
Pokračujícím provádění na aktuálním vláknu se provede synchronní odeslání operace 6–7 do globální fronty – toto blokuje aktuální (hlavní) vlákno, dokud nebude úloha dokončena.
Operace 6–7 se spustí na jiném vláknu a vytiskne "F"
.
Operace k print("G")
je synchronně odeslána do globální fronty a blokuje aktuální pracovní vlákno, dokud nebude dokončeno.
Vytiskne se "G"
a odblokuje se pracovní vlákno, ze kterého byla tato operace odeslána.
Dokončí se operace 6–7, odblokuje se vlákno, ze kterého bylo odesláno (hlavní vlákno), a vytiskne se "H"
.
Po dokončení operace 1–2 se provádění přesune na další operaci v hlavní frontě – operaci 2–5 – která začne a vytiskne "C"
.
Operace 3–4 je zařazena do fronty v hlavní frontě bez blokování vlákna.
Jakmile aktuální operace (2–5) skončí, spustí se provádění další operace (3–4), tiskne se "D"
.
Operace k print("G")
je synchronně odeslána do hlavní fronty a blokuje aktuální vlákno.
Systém pak nekonečně dlouho čeká, až se operace print("E")
a provede se v hlavním vlákně – protože vlákno je zablokováno, vede to k uváznutí.
Odpověď : AIBFGHCD, uváznutí
Úlohy střední obtížnosti zahrnují nejistotu. S takovými problémy se také setkáváme při pohovorech, i když zřídka.
Úkol 7
DispatchQueue.global().async { print("A") } DispatchQueue.global().async { print("B") }
print("A")
je zařazen do fronty asynchronně v globální frontě – bez blokování aktuálního vlákna.print("B")
. V tomto konkrétním případě je do fronty nejprve přidán další úkol a teprve poté jsou zdroje přiděleny do globální fronty. K tomu dochází, protože hlavnímu vláknu je přiděleno nejvíce prostředků a další operace v hlavním vláknu je velmi lehká (pouze operace přidání úkolu) a v praxi probíhá rychleji než alokace prostředků v globální frontě. Opačné scénáře probereme v další části.print("B")
je zařazen do globální fronty."A"
může začít dříve než "B"
, nemůžeme zaručit objednávku, protože tisk není atomická operace (v okamžiku, kdy se výstup objeví v konzole, je blízko konce operace).
Odpověď : (AB)
Závorky označují, že písmena se mohou objevit v libovolném pořadí: buď AB nebo BA.
Úkol 8
print("A") DispatchQueue.main.async { print("B") } DispatchQueue.global().async { print("C") }
Zde si můžeme být jisti pouze tím, že „A“ se vytiskne jako první. Nemůžeme přesně určit, zda se rychleji provede úloha v hlavní frontě nebo v globální frontě.
Odpověď : A(BC)
Úkol 9
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } DispatchQueue.main.async { // 1 print(“B”) }
a
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } print(“B”) // 1
Na jedné straně se v obou případech print("B")
provede na hlavním vlákně. Také nemůžeme přesně určit, kdy budou globální frontě přiděleny zdroje, takže teoreticky může být "A"
vytištěno těsně před dosažením bodu označeného // 1 v hlavním vláknu. V praxi se však první úloha vždy vytiskne jako AB, zatímco druhá jako BA. Důvodem je to, že v prvním případě se print("B")
provede alespoň v další iteraci RunLoop hlavního vlákna (nebo o několik iterací později), zatímco ve druhém případě je print("B")
naplánováno tak, aby se spustil v aktuální iteraci RunLoop v hlavním vlákně. Objednávku však nemůžeme zaručit.
Odpověď na oba úkoly: (AB)
Úkol 10
print("A") DispatchQueue.global().async { print("B") DispatchQueue.global().async { print("C") } print("D") }
Je jasné, že začátek výstupu je "AB"
. Po zařazení print("C")
do fronty nemůžeme přesně určit, kdy pro něj budou přiděleny zdroje – tato úloha se může provést buď před nebo po print("D")
. To se občas stává i v praxi.
Odpověď : AB(CD)
Úkol 11
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { print(“A”) serialQueue.async { print(“B”) } print(“C”) }
Opět nemůžeme přesně určit, kdy budou zdroje přiděleny pro tisk ("B") ve vlastní frontě. V praxi, protože hlavní vlákno má nejvyšší prioritu, "C" se obvykle vytiskne před "B", i když to není zaručeno.
Odpověď : A(BC)
Úkol 12
DispatchQueue.global().async { print("A") } print("B") sleep(1) print("C")
Zde je zřejmé, že výstupem bude BAC, protože jednosekundový spánek zajišťuje, že globální fronta má dostatek času na alokaci zdrojů. Zatímco hlavní vlákno je blokováno spánkem (což byste neměli dělat v produkci), print("A")
se provádí v jiném vlákně.
Odpověď : BAC
Úkol 13
DispatchQueue.main.async { print("A") } print("B") sleep(1) print("C")
V tomto případě, protože print("A")
je zařazen do fronty v hlavní frontě, lze jej spustit pouze v hlavním vláknu. Hlavní vlákno však pokračuje ve vykonávání kódu — tiskne "B"
, pak spí, pak tiskne "C"
. Teprve poté může RunLoop provést úlohu zařazenou do fronty.
Odpověď : BCA
Je nepravděpodobné, že byste se s těmito problémy setkali při pohovorech, ale jejich pochopení vám pomůže lépe pochopit GCD.
Třída Counter se zde používá výhradně pro referenční sémantiku:
final class Counter { var count = 0 }
Úkol 14
let counter = Counter() DispatchQueue.global().async { DispatchQueue.main.async { print(counter.count) } for _ in (0..<100) { // 1 counter.count += 1 } }
Zde lze vytisknout libovolné číslo mezi 0 a 100 v závislosti na tom, jak je hlavní vlákno zaneprázdněné. Jak víme, nemůžeme přesně předpovědět, kdy asynchronní úloha získá prostředky – může se to stát před, během nebo po smyčce na pracovním vláknu.
Odpověď : 0-100
Úkol 15
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } DispatchQueue.global(qos: .userInteractive).async { print(“B”) }
QoS nezaručuje, že fronta s vyšší prioritou obdrží prostředky rychleji, i když se o to iOS pokusí. V praxi je zde výstup (AB).
Odpověď : (AB)
Úkol 16
var count = 0 DispatchQueue.global(qos: . userInitiated).async { for _ in 0..<1000 { count += 1 } print(“A”) } DispatchQueue.global(qos: .userInteractive).async { for _ in 0..<1000 { count += 1 } print(“B”) }
Protože nemůžeme vědět, které provádění začne jako první, ani v rámci 1000 operací nemůžeme určit, která úloha bude dokončena rychleji.
Odpověď : (AB)
Úkol 16.2
Jaký je výstup za předpokladu, že se operace začnou provádět současně?
Vzhledem k tomu, že frontě .userInteractive je přiděleno více zdrojů, během 1000 operací bude provádění v této frontě vždy dokončeno rychleji.
Odpověď : BA
Úkol 17
Pomocí podobného přístupu můžeme upravit jakoukoli úlohu s nejistotou z předchozí části (například úloha 12):
let counter = Counter() let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { serialQueue.async { print(counter.count) } for _ in 0..<100 { counter.count += 1 } }
Lze vytisknout libovolné číslo mezi 0 a 100. Skutečnost, že lze vytisknout 0, potvrzuje, že v úloze 12 nemůžeme zaručit, že výstup "C"
bude vždy před "B"
, protože se v podstatě nic nezměnilo – pouze to, že smyčka je o něco náročnější na zdroje než tisk (všimněte si, že pouhé spuštění smyčky, dokonce ještě před jejím provedením, vedlo v praxi k naprosté nejistotě).
Odpověď : 0-100
Úkol 18
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } print(“B”) DispatchQueue.global(qos: .userInteractive).async { print(“C”) }
Zde nastává podobná situace. Teoreticky může print("A")
probíhat rychleji než print("B")
(pokud nahradíte print("B")
něčím o něco těžším). V praxi se "B"
vždy vytiskne jako první. Skutečnost, že provedeme print("B")
před zařazením do fronty print("C")
značně zvyšuje pravděpodobnost, že "A"
bude vytištěno před "C"
, protože čas navíc strávený print("B")
v hlavním vláknu často postačuje k tomu, aby fronta .userInitiated získala prostředky a provedla print("A")
. To však není zaručeno a někdy se může písmeno "C"
vytisknout rychleji. Teoreticky tedy existuje úplná nejistota; v praxi to bývá B(CA).
Odpověď : (BCA)
Úkol 19
DispatchQueue.global().sync { print(Thread.current) }
Dokumentace k synchronizaci uvádí:
"V rámci optimalizace výkonu tato funkce spouští bloky v aktuálním vláknu, kdykoli je to možné, s jednou výjimkou: bloky odeslané do hlavní fronty pro odeslání vždy běží v hlavním vláknu."
To znamená, že pro účely optimalizace se mohou synchronní volání provádět ve stejném vlákně, ze kterého byla volána (s výjimkou main.sync
– úlohy, které jej používají, se vždy provádějí v hlavním vláknu). Vytiskne se tedy aktuální (hlavní) vlákno.
Odpověď : hlavní vlákno
Úkol 20
DispatchQueue.global().sync { // 1 print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Vytiskne se pouze "A"
protože dojde k uváznutí. Kvůli optimalizaci se úloha (označená 1) začne provádět v hlavním vláknu a pak volání main.sync
vede k uváznutí.
Odpověď : Á, uváznutí
Úkol 21
DispatchQueue.main.async { print("A") DispatchQueue.global().sync { print("B") } print("C") }
Optimalizace způsobí, že úloha print("B")
nebude zařazena do fronty, ale bude "spojena" do aktuálního prováděcího vlákna. Tedy kód:
DispatchQueue.global().sync { print("B") }
se stává ekvivalentem:
print(“B”)
Odpověď : ABC
Z těchto úloh je zřejmé, že main.sync musíte používat velmi opatrně – pouze když jste si jisti, že volání není provedeno z hlavního vlákna.
V tomto článku jsme se zaměřili na základní koncepty multithreadingu v iOS – vlákna, úlohy a fronty – a jejich vzájemné vztahy. Prozkoumali jsme, jak GCD spravuje provádění úloh v hlavních, globálních a vlastních frontách, a diskutovali jsme o rozdílech mezi sériovým a souběžným prováděním. Kromě toho jsme zkoumali kritické rozdíly mezi synchronním (synchronním) a asynchronním (asynchronním) odesíláním úloh, přičemž jsme zdůraznili, jak tyto přístupy ovlivňují pořadí a načasování provádění kódu. Zvládnutí těchto základních konceptů je nezbytné pro vytváření citlivých, stabilních aplikací a pro vyhnutí se běžným nástrahám, jako jsou uváznutí.
Doufám, že jste v tomto článku našli něco užitečného. Pokud bude cokoliv nejasné, neváhejte mě kontaktovat pro bezplatné vysvětlení na Telegramu: @kfamyn .
sync
- https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)-3segw