Салам! Менин атым Кирил Фамин, мен iOS иштеп чыгуучусумун.
Бул макалада биз Grand Central Dispatch (GCD) жөнүндө биротоло чагылдырабыз. Swift Modern Concurrency азыр GCD эскирип калгандай сезилиши мүмкүн, бирок бул негизди колдонгон код өндүрүштө да, интервьюларда да көп жылдар бою пайда боло берет.
Бүгүн биз GCD фундаменталдык түшүнүгүнө гана токтолобуз. Биз көп агымдын негизги аспектилерин гана майда-чүйдөсүнө чейин карап чыгабыз, анын ичинде кезектер менен жиптердин ортосундагы байланыш — бул теманы башка көптөгөн макалалар көңүл бурбай калат. Бул түшүнүктөрдү эске алуу менен, DispatchGroup
, DispatchBarrier
, семафора , мутекс жана башкалар сыяктуу темаларды түшүнүү сизге жеңил болот.
Бул макала үйрөнчүктөр жана тажрыйбалуу иштеп чыгуучулар үчүн пайдалуу болот. Мен техникалык терминдерди ашыкча жүктөөдөн алыс болуп, баарын ачык-айкын тил менен түшүндүрүүгө аракет кылам.
Content Overview
- Негизги түшүнүктөр: жип, көп агым, GCD, тапшырма, кезек
- Кезектин түрлөрү: негизги, глобалдык, салттуу
- Кезектин артыкчылыктары: Кызматтын сапаты (QoS)
- Сериялык жана параллелдүү кезектер
- Тапшырмаларды аткаруунун жолдору: синхрондоштуруу, синхрондоштуруу
- Туюк
- GCD көнүгүүлөр
- Шилтемелер
Негизги түшүнүктөр: Thread, Multithreading, GCD, Task, and Queue
Thread - негизинен, системалык көрсөтмөлөрдүн топтому жайгаштырылган жана аткарылуучу контейнер. Чынында, бардык аткарылуучу код кандайдыр бир жипте иштейт. Биз негизги жип менен жумушчу жипти айырмалайбыз.
Multithreading – системанын бир нече жипти бир убакта (бир убакта) аткаруу мүмкүнчүлүгү. Бул коддун бир нече бутактарынын параллелдүү иштешине мүмкүндүк берет.
Grand Central Dispatch (GCD) – жиптер менен иштөөнү жеңилдеткен алкак (көп агымдын артыкчылыктарын колдонуу). Анын негизги примитивдери - тапшырмалар жана кезектер.
Ошентип, GCD бир эле учурда аткарылуучу кодду жазууну жеңилдеткен курал. Жөнөкөй мисал, негизги жиптеги UI жаңыртууларына тоскоол болбоо үчүн оор эсептөөлөрдү өзүнчө жипке түшүрүү.
Тапшырма – иштеп чыгуучу тарабынан топтоштурулган нускамалардын жыйындысы. Кайсы код кайсы бир тапшырмага таандык экенин иштеп чыгуучу чечээрин түшүнүү маанилүү.
Мисалы:
print(“GCD”) // a task
let database = Database() let person = Person(age: 23) // also a task database.store(person)
Кезек - GCDнин негизги примитиви, бул иштеп чыгуучу тапшырмаларды аткаруу үчүн койгон жер. Кезек жиптер арасында тапшырмаларды бөлүштүрүү жоопкерчилигин алат (ар бир кезекте системанын жип бассейнине кирүү мүмкүнчүлүгү бар).
Негизи, кезектер жиптерди түз башкаруунун ордуна, кодуңузду тапшырмаларга уюштурууга көңүл бурууга мүмкүндүк берет. Тапшырманы кезекке жөнөткөнүңүздө, ал жеткиликтүү жипте аткарылат — көбүнчө тапшырманы жөнөтүүдө колдонулгандан башкача.
Сиз бардык GIFтердин mp4 версияларын таба аласыз
Кезектин түрлөрү
Негизги кезек – негизги жипте гана аткарылуучу кезек. Бул сериялык (бул тууралуу кийинчерээк).
let mainQueue = DispatchQueue.main
Глобалдык кезектер – система тарабынан берилген 5 кезек (ар бир артыкчылык деңгээли үчүн бирден) бар. Алар параллелдүү.
let globalQueue = DispatchQueue.global()
Ыңгайлаштырылган кезектер - иштеп чыгуучу тарабынан түзүлгөн кезектер. Иштеп чыгуучу 5 приоритеттин бирин жана түрүн тандайт: сериялык же параллелдүү (демейки боюнча, алар сериялык).
let userQueue = DispatchQueue(label: “com.kirylfamin.concurrent”, attributes: .concurrent).
Кезектин артыкчылыктары
Кызматтын сапаты (QoS) – кезек приоритеттеринин системасы. Тапшырма кезекте турган кезектин артыкчылыктуулугу канчалык жогору болсо, ага ошончолук көп ресурстар бөлүнөт. Жалпысынан 5 QoS деңгээли бар:
.userInteractive
– эң жогорку артыкчылык. Ал дароо аткарууну талап кылган, бирок негизги жипте иштетүүгө ылайыктуу эмес тапшырмалар үчүн колдонулат. Мисалы, реалдуу убакыт режиминде сүрөттү ретуш кылууга мүмкүндүк берген колдонмодо ретуштун натыйжасы заматта эсептелиши керек; бирок, эгер негизги жипте аткарылса, ал ар дайым негизги жипте пайда болгон UI жаңыртууларына жана жаңсоолорду иштетүүгө тоскоол болот (мисалы, колдонуучу манжасын ретушка өткөрүлө турган аймакка жылдырганда жана колдонмо натыйжаны заматта "манжа астында" көрсөтүшү керек). Ошентип, биз негизги жипти жүктөбөй, мүмкүн болушунча тез натыйжа алабыз.
.userInitiated
– интерактивдүү тапшырмалардай маанилүү болбосо да, тез кайтарым байланышты талап кылган тапшырмалар үчүн артыкчылык. Ал, адатта, колдонуучу тапшырма дароо бүтпөй турганын жана күтүүгө туура келерин түшүнгөн тапшырмалар үчүн колдонулат (мисалы, сервердин өтүнүчү).
.default
– стандарттык артыкчылык. Ал кезекти түзүүдө иштеп чыгуучу QoS көрсөтпөсө – тапшырмага конкреттүү талаптар жок болгондо жана анын приоритети контексттен аныкталбаганда дайындалат (мисалы, тапшырманы .userInitiated приоритеттүү кезектен чакырсаңыз, тапшырма ошол артыкчылыкты мурастайт).
.utility
– колдонуучунун дароо пикирлерин талап кылбаган, бирок колдонмонун иштеши үчүн зарыл болгон тапшырмалар үчүн артыкчылык. Мисалы, сервер менен маалыматтарды синхрондоштуруу же дискке автосактоо жазуу.
.background
– эң төмөнкү артыкчылык. Мисалы, кэш тазалоо.
Бардык кезектер Сериялык кезектер же Кошулма кезектер деп классификацияланат
Сериялык кезектер - аты айтып тургандай, бул тапшырмалар биринин артынан бири аткарылуучу кезектер. Бул кийинки тапшырма учурдагы аткарылгандан кийин гана башталат дегенди билдирет.
Кошумча кезектер – Бул кезектер тапшырмаларды параллелдүү аткарууга мүмкүндүк берет – мурунку тапшырмалар аткарылганына карабастан, ресурстар бөлүнгөндө жаңы тапшырма башталат. Көңүл буруңуз, баштоо тартиби гана кепилденет (мурда кезекке коюлган тапшырма кийинчерээк башталат ), бирок бүтүрүү тартибине кепилдик жок.
Тапшырмаларды кантип аткаруу керек
Белгилей кетчү нерсе, биз азыр чакыруучу жипке салыштырмалуу тапшырмаларды аткаруу ыкмаларын талкуулап жатабыз. Башка сөз менен айтканда, тапшырманы чакыруу ыкмасы сиз тапшырманы кезекке жөнөткөн жипте окуялардын кандайча өрчүшүн аныктайт.
Асинхрондуу ( async
)
Асинхрондук чалуу - бул чакыруучу жип бөгөттөлбөгөн, башкача айтканда, ал кезекке турган тапшырманын аткарылышын күтпөйт.
DispatchQueue.main.async { print(“A”) } print(“B”)
Бул мисалда, биз асинхрондук түрдө тапшырманы print("A")
чыгарабыз ( анткени бул код кандайдыр бир конкреттүү кезектин ичинде болбогондуктан, ал демейки боюнча негизги жипте аткарылат). Ошентип, биз негизги жип боюнча тапшырманы күтпөйбүз жана дароо аткарууну улантабыз. Бул конкреттүү мисалда print("A")
тапшырмасы негизги кезекке тизилип, андан кийин print("B")
негизги жипте дароо аткарылат. Негизги жип учурдагы кодду аткаруу менен алек болгондуктан (жана негизги кезектеги тапшырмалар негизги жипте гана аткарылышы мүмкүн), учурдагы тапшырманы print("B")
биринчи бүтөт жана негизги жип бош болгондон кийин гана негизги кезекте турган кезекке коюлган тапшырма print("A")
иштейт. чыгаруу болуп саналат: BA.
DispatchQueue.global().async { updateData() DispatchQueue.main.async { updateInterface() } Logger.log(.success) } indicateLoading()
Биз негизги жиптен демейки артыкчылыкка ээ болгон глобалдык кезекке тапшырманы асинхрондук түрдө кошобуз — ошондуктан чакыруучу жип дароо уланат жана чалуулар
indicateLoading()
.
Бир нече убакыт өткөндөн кийин, система тапшырма үчүн ресурстарды бөлүштүрөт жана аны жип бассейнинен эркин жумушчу жипте аткарат жана
updateData()
чакырат.
updateInterface()
камтыган тапшырма негизги кезекке асинхрондук түрдө тизилет — чакыруучу жумушчу жип анын бүтүшүн күтпөйт жана улантат.
Тапшырмалар асинхрондук түрдө тизилгендиктен, ресурстар качан бөлүштүрүлөөрүн так айта албайбыз. Бул учурда,
updateInterface()
(негизги жипте) жеLogger.log(.success)
(жумушчу жипте) биринчи аткара аларын так айта албайбыз (1-2-кадамдарда да аткара албайбыз: кайсы биринчи аткарылат, негизги жиптеindicateLoading()
же жумушчу жиптеupdateData()
көрсөтүлөт). Негизги жип UI жаңыртуулары, жаңсоолорду иштетүү жана башка негизги тапшырмаларды аткаруу менен алек болсо да, ал дайыма системанын максималдуу ресурстарын алат. Башка жагынан алганда, жумушчу жип боюнча аткаруу үчүн ресурстар да дээрлик дароо бөлүнүшү мүмкүн.
Бул анимацияда глобалдык кезек өз милдеттерин кээ бир эркин жумушчу жипте аткарарын эске алыңыз
Синхрондуу ( sync
)
Синхрондук чалуу - бул чакырып жаткан жип токтоп, кезекке койгон тапшырманын аягына чыгышын күткөн чалуу.
let userQueue = DispatchQueue(label: "com.kirylfamin.serial") DispatchQueue.global().async { var account = BankAccount() userQueue.sync { account.balance += 10 } let balance = account.balance print(balance) }
Бул жерде, глобалдык кезекте тапшырманы аткарып жаткан жумушчу жиптен биз балансты көбөйтүү үчүн тапшырманы синхрондуу түрдө ыңгайлаштырылган кезекке киргизебиз. Учурдагы жип бөгөттөлгөн жана кезекке коюлган тапшырма бүткүчө күтөт. Ошентип, баланс ыңгайлаштырылган кезектеги тапшырма өсүүнү аяктагандан кийин гана басылып чыгат.
Эскертүү: Жогорудагы анимацияда ыңгайлаштырылган кезек кээ бир акысыз жумушчу жипте өз милдеттерин аткарат
Туюк
Синхрондук тапшырмалардын контекстинде туюкту талкуулоо маанилүү - жип же жиптер өзүн же бири-бирин улантуу үчүн чексиз күтүп турганда. Эң кеңири таралган мисал - негизги жиптен DispatchQueue.main.sync {} чалуу.
Негизги жип учурдагы тапшырманы аткаруу менен алек, анын ичинде биз кандайдыр бир кодду синхрондуу түрдө аткаргыбыз келет. Ошентип, синхрондуу чалуу негизги жипти бөгөттөйт. Тапшырма негизги кезекте турат, бирок баштоо мүмкүн эмес, анткени негизги жип учурдагы тапшырманын бүтүшүн күтүп бөгөттөлгөн — жана негизги кезектеги тапшырмалар негизги жипте гана иштей алат. Муну алгач элестетүү кыйын болушу мүмкүн, бирок негизгиси DispatchQueue.main.sync
менен кезекке коюлган тапшырма учурдагы тапшырманын бир бөлүгү болуп калат жана биз аны учурдагы тапшырмадан кийин кезекке коюп жатабыз. Натыйжада жип учурдагы тапшырманын бир бөлүгүн күтөт, ал башталбайт, анткени жипти учурдагы тапшырма ээлейт.
func printing() { print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Негизги кезектеги print("B")
аткарылбай тургандыгына көңүл буруңуз , анткени негизги жип бөгөттөлгөн.
GCD Exercises
Бул бөлүмдө, буга чейин алынган бардык билимдер менен, биз ар кандай татаалдыктагы көнүгүүлөрдү талкуулайбыз: сиз интервьюларда жолуга турган жөнөкөй код блокторунан баштап, конкурренттик программалоону түшүнүүгө түрткөн өнүккөн чакырыктарга чейин. Бардык бул тапшырмалардагы суроо: Консолго эмне басып чыгарылат?
Эсиңизде болсун, негизги кезек сериялык, глобалдык() кезектер бир убакта болот жана кээде көйгөй белгилүү атрибуттары бар ыңгайлаштырылган кезектерди камтышы мүмкүн.
Негизги көнүгүүлөр
Биз кадимки кыйынчылыктагы тапшырмалардан баштайбыз - өндүрүштүн белгисиздик мүмкүнчүлүгү аз. Бул тапшырмалар интервьюларда пайда болушу мүмкүн; негизги убакытты алуу жана кылдат маселени талдоо болуп саналат.
Бардык көнүгүүлөрдүн толук кодун бул жерден таба аласыз.
1-тапшырма
print(“A”) DispatchQueue.main.async { print(“B”) } print(“C”)
- Негизги жипте
print("A")
аткарылат. -
print("B")
негизги кезекте асинхрондук түрдө тизилген. Негизги жип бош эмес болгондуктан, бул тапшырма кезекте күтүп турат. - Негизги жипте
print("C")
аткарылат. - Негизги жип бош болгондо (мурунку тапшырма аяктагандан кийин, негизги кезектеги UI жаңыртуулары, жаңсоолорду башкаруу ж.б. сыяктуу тапшырмалар эле эмес, негизги жипте иштетилиши керек болгон башка окуялар болушу мүмкүн. Тереңирээк түшүнүү үчүн, RunLoop жөнүндө көбүрөөк окуп чыгыңыз) кезекке коюлган тапшырманы
print("B")
аткарылат.
Жооп : ACB
2-тапшырма
print(“A”) DispatchQueue.main.async { print(“B”) } DispatchQueue.main.async { print(“C”) } print(“D”)
- Негизги жипте
print("A")
аткарылат. -
print("B")
негизги кезекте турат. Негизги жип жеткиликтүү болгонго чейин негизги кезек. -
print("C")
print("B") кийин кезекке коюлат жана ошондой эле күтөт. - Негизги жип аткарууну улантып, "D" басып чыгарат.
- Негизги жип жеткиликтүү болгондо (башка RunLoop тапшырмаларын аткаргандан кийин), биринчи кезекте турган операция—
print("B")
— аткарылат. - Негизги жип кайра бош болгондон кийин (башка RunLoop тапшырмаларын аткаргандан кийин—келечекте бул деталды калтырып кетем, анткени ал жалпы тартипке таасирин тийгизбейт),
print("C")
аткарылат.
Жооп : ADBC
Мен кээ бир мисалдарда түшүндүрүүнү бир аз жөнөкөйлөтүп, система синхрондук чалуулардын аткарылышын оптималдаштырган фактыны калтырып кетем, бул тууралуу кийинчерээк талкуулайбыз.
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")
негизги жипте аткарылат. - Асинхрондук тапшырма (1–3 деп белгиленген) учурдагы (негизги) жипти бөгөттөп туруп, негизги кезекке тизилет.
- Негизги жип аткарууну улантып,
"F"
басып чыгарат. -
print("G")
операциясы мурунку тапшырмадан кийин негизги кезекке коюлат (1–3-кадамдар). - Негизги жип бош болгондон кийин, биринчи кезекте турган операция—
print("B")
— аткарыла баштайт. - Андан кийин
print("C")
операциясы негизги кезекке коюлат (мында учурдагы тапшырма дагы эле аткарылууда жанаprint("G")
кезектеги аны ээрчийт). Ал асинхрондук түрдө кошулгандыктан, биз анын аткарылышын күтпөйбүз жана дароо улантабыз. - Андан кийин,
print("D")
операциясы глобалдык кезекке коюлат. Бул чалуу синхрондуу болгондуктан, улантуудан мурун глобалдык кезек аны аткарганга чейин күтөбүз (ал каалаган жумушчу жипте иштеши мүмкүн). - Акырында,
print("E")
операциясы негизги кезекке тизилет. Бул чалуу синхрондуу болгондуктан, тапшырма аяктаганга чейин учурдагы жип бөгөттөлүшү керек. Бирок, негизги кезекте буга чейин тапшырмалар бар жана алардан кийин аягынаprint("E")
операциясы кошулат. Демек,print("E")
иштетиле электе, бул операциялар биринчи аткарылышы керек. Бирок негизги жип дагы эле учурдагы операцияны аткаруу менен алек, ошондуктан кийинки кезектеги операцияларга өтө албайт. Учурдагы операциядан кийин"G"
жана"C"
басып чыгаруу операциялары болбосо дагы, жип дагы деле уланта алган жок, анткени учурдагы операция (1–3-кадамдар) аягына чыга элек.
- Эгерде чалуу асинхрондуу болсо, print("E") операциясы
"G"
жана"C"
басып чыгаруу операцияларынан кийин жөн эле кезекке коюлмак.
Жооп : AFBD
Альтернативдүү жооп (эгерде экинчи чалуу async
болсо): AFBDGCE
4-тапшырма
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”) serialQueue.async { // 1 print(“A”) serialQueue.sync { print(“B”) } print(“C”) } // 2
Тапшырма (1–2-кадамдар) ыңгайлаштырылган сериялык кезекке асинхрондук түрдө кезекке коюлат (демейки шартта, биз
.concurrent
атрибутун колдонбогондуктан, кезектер сериялык болуп саналат).- Система ресурстарды бөлүштүргөндө, аткаруу башталат жана
"A"
басылып чыгат. - Ошол эле сериялык кезектин ичинде
print("B")
кезекке коюлат. Чакыруу синхрондуу болгондуктан, жип анын аткарылышын күтүүдө. - Бирок, кезек сериялуу болгондуктан жана дагы эле тышкы тапшырма 1-2 менен алек болгондуктан,
print("B")
тапшырмасы башталбайт, натыйжада туюк пайда болот.
Жооп : А, туюк
Бул мисал негизги кезек болобу же ыңгайлаштырылган кезек болобу, туюк туюк ар кандай сериялык кезекте болушу мүмкүн экенин көрсөтүп турат.
5-тапшырма
Мурунку тапшырмадан сериялык кезекти параллелдүү менен алмаштыралы.
DispatchQueue.global().async { // 1 print("A") DispatchQueue.global().sync { print("B") } print("C") } // 2
- Тапшырма (1–2-кадамдар) глобалдык (бир убактагы) кезекке асинхрондук түрдө тизилет.
- Ресурстар бөлүнгөндө, аткаруу башталат жана
"A"
басылып чыгат. - Ошол эле глобалдык кезекте
print("B")
аткаруу үчүн синхрондуу чакыруу жасалат, ал тапшырма аяктаганга чейин учурдагы жумушчу жипти бөгөттөйт. - Бул учурда, жип бөгөттөлсө да, глобалдык кезек бир убакта болгондуктан, ал учурдагы операциянын бүтүшүн күтпөстөн, кийинки операцияны башка жипте иштетүү менен гана аткара башташы мүмкүн. Ошентип, чакыруучу жип
print("B")
тапшырмасынын башка жумушчу жипте аткарылышын күтөт. - Тапшырма аяктагандан кийин, баштапкы чакыруучу жип бөгөттөн чыгарылат,
"C"
басылып чыгат.
Жооп : ABC
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")
Негизги жип
"A"
басып чыгарат.Асинхрондук тапшырма (1–8-кадамдар) учурдагы жипти бөгөтпөстөн негизги кезекке тизилет.
Негизги жип уланып,
"I"
басып чыгарат.Кийинчерээк, негизги жип бош болгондо, негизги кезекке коюлган тапшырма аткарыла баштайт жана
"B"
басып чыгарат.Дагы бир асинхрондук тапшырма (2–5-кадамдар) негизги кезекте турат - учурдагы жипти бөгөттөбөйт.
Учурдагы жипте аткарууну улантуу менен 6–7 операциясынын синхрондуу жөнөтүлүшү глобалдык кезекке жасалат — бул тапшырма аяктаганга чейин учурдагы (негизги) жипти бөгөттөйт.
Операция 6–7 башка жипте аткарыла баштайт,
"F"
басып чыгарат.print("G")
синхрондуу түрдө глобалдык кезекке жөнөтүлүп, учурдагы жумушчу жип ал аяктаганга чейин бөгөттөлөт."G"
басып чыгарылат жана бул операция жөнөтүлгөн жумушчу жип бөгөттөн чыгарылат.6–7 операция аяктап, ал жөнөтүлгөн жипти (негизги жип) бөгөттөн чыгарат жана
"H"
басылып чыгат.1-2-операция аяктагандан кийин, аткаруу негизги кезекте кийинки операцияга — 2-5-операцияга өтөт, ал башталат жана
"C"
басып чыгарат.3-4-операция жипти тоспой негизги кезекке тизилет.
Учурдагы операция (2–5) аяктагандан кийин, аткаруу кийинки операцияда (3–4) башталат,
"D"
басып чыгарылат.print("G")
учурдагы жипти бөгөттөө менен негизги кезекке синхрондуу жөнөтүлөт.Андан кийин система негизги жипте аткарылышы үчүн операцияны
print("E")
чексиз күтөт — жип бөгөттөлгөндүктөн, бул туюкка алып келет.
Жооп : AIBFGHCD, туюк
Ортодогу көнүгүүлөр
Орточо кыйынчылыктагы тапшырмалар белгисиздикти камтыйт. Мындай көйгөйлөр сейрек болсо да, интервьюларда да кездешет.
7-тапшырма
DispatchQueue.global().async { print("A") } DispatchQueue.global().async { print("B") }
-
print("A")
глобалдык кезекке асинхрондук түрдө кезекке коюлат — учурдагы жипти бөгөттөбөй. - Биз системанын глобалдык кезектеги тапшырма үчүн ресурстарды бөлүүсүн күтөбүз. Теориялык жактан алганда, бул ар кандай учурда болушу мүмкүн, жада калса
print("B")
кезектеги буйругун аткарганга чейин. Бул өзгөчө учурда, кезектеги тапшырма биринчи кезекте кошулат, андан кийин гана ресурстар глобалдык кезекке бөлүнөт. Бул негизги жипке эң көп ресурстар бөлүнгөндүктөн, негизги жип боюнча кийинки операция өтө жеңил (жөн гана тапшырманы кошуу операциясы) жана иш жүзүндө глобалдык кезектеги ресурстарды бөлүштүрүүгө караганда тезирээк ишке ашат. Биз кийинки бөлүмдө карама-каршы сценарийлерди талкуулайбыз. -
print("B")
глобалдык кезекте турат. - Ошол эле учурда, глобалдык кезек ресурстарды бөлүштүрүүнү күтүп жатканда, негизги жип уланат.
- Ресурстар жеткиликтүү болгондо, эки тапшырма тең аткарылат.
"A"
басып чыгаруу тапшырмасы"B"
дан эрте башталса да, биз тартипке кепилдик бере албайбыз, анткени басып чыгаруу атомдук операция эмес (чыгарма консолдо пайда болгон учур операциянын аягына жакын).
Жооп : (AB)
кашаалар тамгалар каалаган тартипте пайда болушу мүмкүн экенин көрсөтүп турат: же AB же BA.
8-тапшырма
print("A") DispatchQueue.main.async { print("B") } DispatchQueue.global().async { print("C") }
Бул жерде, биз бир гана "А" биринчи басылган деп ишенсек болот. Негизги кезектеги же глобалдык кезектеги тапшырма тезирээк аткарыларын так аныктай албайбыз.
Жооп : A(BC)
9-тапшырма
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } DispatchQueue.main.async { // 1 print(“B”) }
жана
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } print(“B”) // 1
Бир жагынан, эки учурда тең print("B")
негизги жипте аткарылат. Ошондой эле, глобалдык кезек ресурстар качан бөлүнөрүн так аныктай албайбыз, ошондуктан теориялык жактан алганда, "A"
негизги жипте // 1 белгиленген чекитке жеткенге чейин дароо басылып чыгышы мүмкүн. Бирок иш жүзүндө, биринчи тапшырма дайыма AB катары, экинчиси BA катары басып чыгарат. Себеби, биринчи учурда, print("B")
жок дегенде негизги жиптин кийинки RunLoop итерациясында (же бир нече итерациядан кийин) аткарылат, ал эми экинчи учурда, print("B")
негизги жипте учурдагы RunLoop итерациясында иштеши пландаштырылган. Бирок, биз тартипке кепилдик бере албайбыз.
Эки тапшырмага жооп : (AB)
10-тапшырма
print("A") DispatchQueue.global().async { print("B") DispatchQueue.global().async { print("C") } print("D") }
Бул чыгаруунун башталышы экени түшүнүктүү "AB"
. print("C")
кезекке койгондон кийин, биз ага ресурстар качан бөлүнөрүн так аныктай албайбыз — бул тапшырма print("D")
чейин же андан кийин аткарылышы мүмкүн. Бул кээде практикада да болот.
Жооп : AB(CD)
11-тапшырма
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { print(“A”) serialQueue.async { print(“B”) } print(“C”) }
Дагы бир жолу, ыңгайлаштырылган кезектеги басып чыгарууга («В») ресурстар качан бөлүнөрүн так аныктай албайбыз. Иш жүзүндө, негизги жипке эң чоң артыкчылык берилгендиктен, "С" адатта "В" алдында басып чыгарат, бирок бул кепилдик берилбейт.
Жооп : A(BC)
12-тапшырма
DispatchQueue.global().async { print("A") } print("B") sleep(1) print("C")
Бул жерде, бир секунддук уйку глобалдык кезек ресурстарды бөлүштүрүү үчүн жетиштүү убакыт бар экенин камсыз кылат, анткени чыгаруу BAC болору айдан ачык. Негизги жип уйку менен бөгөттөлсө (өндүрүштө муну жасабашыңыз керек), print("A")
башка жипте аткарат.
Жооп : BAC
13-тапшырма
DispatchQueue.main.async { print("A") } print("B") sleep(1) print("C")
Бул учурда, print("A")
негизги кезекте тургандыктан, ал негизги жипте гана аткарылышы мүмкүн. Бирок, негизги жип кодду аткарууну улантат — басып чыгаруу "B"
, андан кийин уктап, андан кийин басып чыгаруу "C"
. Ошондон кийин гана RunLoop кезекке коюлган тапшырманы аткара алат.
Жооп : BCA
Өркүндөтүлгөн тапшырмалар
Интервьюларда бул көйгөйлөргө туш болушуңуз күмөн, бирок аларды түшүнүү GCDди жакшыраак түшүнүүгө жардам берет.
Бул жерде Counter класс шилтеме семантикасы үчүн гана колдонулат:
final class Counter { var count = 0 }
14-тапшырма
let counter = Counter() DispatchQueue.global().async { DispatchQueue.main.async { print(counter.count) } for _ in (0..<100) { // 1 counter.count += 1 } }
Бул жерде 0 жана 100 ортосундагы каалаган сан негизги жип канчалык бош эмес экенине жараша басылышы мүмкүн. Белгилүү болгондой, асинхрондук тапшырма ресурстарды качан алаарын алдын ала айта албайбыз — бул жумушчу жиптеги циклден мурун, учурунда же андан кийин болушу мүмкүн.
Жооп : 0-100
15-тапшырма
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } DispatchQueue.global(qos: .userInteractive).async { print(“B”) }
QoS артыкчылыктуу кезек ресурстарды тезирээк алат деп кепилдик бербейт, бирок iOS буга аракет кылат. Иш жүзүндө бул жерде жыйынтык (AB).
Жооп : (AB)
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”) }
Кайсы иштин биринчи башталарын биле албагандыктан, 1000 операцияда да кайсы тапшырма тезирээк бүтөөрүн аныктай албайбыз.
Жооп : (AB)
16.2-милдет
Операциялар бир убакта аткарыла баштаганда кандай жыйынтык чыгат?
.userInteractive кезегине көбүрөөк ресурстар бөлүнгөндүктөн, 1000 операциянын аралыгында ал кезектеги аткарылышы дайыма тезирээк бүтөт.
Жооп : БА
17-тапшырма
Окшош ыкманы колдонуу менен, биз мурунку бөлүмдөгү белгисиздик менен каалаган тапшырманы өзгөртө алабыз (мисалы, 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 } }
0дөн 100гө чейинки каалаган санды басып чыгарууга болот. 0 басып чыгарылышы мүмкүн экендиги 12-тапшырмада биз "C"
чыгышы дайыма "B"
га чейин боло тургандыгына кепилдик бере албай турганыбызды ырастайт, анткени негизи эч нерсе өзгөргөн жок — цикл басып чыгарууга караганда бир аз көбүрөөк ресурсту талап кылат (белгилеп кетсек, циклди жөн эле баштоо, ал тургай анын аткарылышына чейин, иш жүзүндө толук белгисиздикке алып келген).
Жооп : 0-100
18-тапшырма
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } print(“B”) DispatchQueue.global(qos: .userInteractive).async { print(“C”) }
Ушундай эле жагдай бул жерде да болот. Теорияда print("A")
print("B")
караганда тезирээк аткарылышы мүмкүн (эгерде print("B")
бир аз оор нерсе менен алмаштырылса). Иш жүзүндө "B"
ар дайым биринчи басып чыгарат. Бирок, print print("B")
print("C")
аткарганыбыз "A"
"C"
га чейин басылып чыгуу ыктымалдыгын бир топ жогорулатат, анткени негизги жипте print("B")
кошумча убакыт көп учурда .userInitiated кезеги үчүн ресурстарды алуу жана print("A")
аткаруу үчүн жетиштүү болот. Ошентсе да, бул кепилдик берилбейт жана кээде "C"
тезирээк басып чыгарышы мүмкүн. Ошентип, теорияда толук белгисиздик бар; иш жүзүндө, ал B(CA) болот.
Жооп : (BCA)
19-тапшырма
DispatchQueue.global().sync { print(Thread.current) }
Синхрондоштуруу документтеринде төмөнкүлөр айтылат:
"Аткаруу оптималдаштыруу катары, бул функция мүмкүн болушунча учурдагы жипте блокторду аткарат, бирден башкасы: негизги диспетчердик кезекке берилген блоктор ар дайым негизги жипте иштейт."
Бул оптималдаштыруу максатында, синхрондук чалуулар алар чакырылган жипте аткарылышы мүмкүн дегенди билдирет ( main.sync
кошпогондо – аны колдонгон тапшырмалар ар дайым негизги жипте аткарылат). Ошентип, учурдагы (негизги) жип басылган.
Жооп : негизги тема
20-тапшырма
DispatchQueue.global().sync { // 1 print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Туюктук пайда болгондуктан, "A"
гана басылып чыгат. Оптимизациядан улам тапшырма (1 деп белгиленген) негизги жипте аткарыла баштайт, андан кийин main.sync
чакырылышы туюкка алып келет.
Жооп : А, туюк
21-тапшырма
DispatchQueue.main.async { print("A") DispatchQueue.global().sync { print("B") } print("C") }
Оптималдаштыруу print("B")
тапшырмасын кезекке койбостон, учурдагы аткаруу жипине "кошулушуна" алып келет. Ошентип, код:
DispatchQueue.global().sync { print("B") }
барабар болуп калат:
print(“B”)
Жооп : ABC
Бул тапшырмалардан көрүнүп тургандай, сиз main.syncти өтө кылдаттык менен колдонушуңуз керек — чалуу негизги жиптен жасалган эмес экенине ынанганда гана.
Корутунду
Бул макалада биз iOS'то көп агымдын негизги концепцияларына — жиптерге, тапшырмаларга жана кезектерге жана алардын өз ара байланыштарына токтолдук. Биз GCD негизги, глобалдуу жана ыңгайлаштырылган кезектер боюнча тапшырмаларды аткарууну кантип башкарарын изилдеп, сериялык жана бир убакта аткаруунун ортосундагы айырмачылыктарды талкууладык. Кошумчалай кетсек, биз синхрондуу (синхрондоштуруу) жана асинхрондук (асинхрондук) тапшырманы жөнөтүүнүн ортосундагы критикалык айырмачылыктарды карап чыктык, бул ыкмалар коддун аткарылышынын тартибине жана убактысына кандай таасир этээрин баса белгиледи. Бул негизги түшүнүктөрдү өздөштүрүү жооп берүүчү, туруктуу тиркемелерди түзүү жана туңгуюктар сыяктуу жалпы тузактардан качуу үчүн абдан маанилүү.
Бул макалада сиз пайдалуу нерсе таптыңыз деп үмүттөнөм. Эгер бир нерсе түшүнүксүз болуп калса, Telegram аркылуу акысыз түшүндүрмө алуу үчүн мени менен байланышыңыз: @kfamyn .
Тиешелүү шилтемелер
- Бардык анимациялар менен YouTube каналы - https://www.youtube.com/@kirylfamin
- Көнүгүүлөрдүн толук коду - https://github.com/kfamyn/GCD-Tasks
- Менин Telegram - http://t.me/kfamyn
- RunLoop - https://developer.apple.com/documentation/foundation/runloop
-
sync
ыкмасынын документтери - https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)-3segw