Од C хакова до C++20: Еволуција корутина

Последње ажурирање: 12/27/2025
  • Корутине генерализују потпрограме очувањем локалног стања и настављањем извршавања у тачкама обуставе, омогућавајући природно изражавање машина стања, генератора и кооперативне конкурентности.
  • C имплементације су еволуирале од ручне манипулације стеком и POSIX контекстних API-ја до апроксимација заснованих на макроима и преносивих библиотека корутина изграђених на промени контекста на нивоу корисника.
  • C++20 стандардизује модел корутина без стека са обећањима, co_await, co_yield и оквире корутина, омогућавајући библиотекама да дефинишу апстракције асинхроних и генераторских функција високог нивоа.
  • Стандардизовани модел, у комбинацији са чекачима и прилагођеним типовима обећања, обједињује употребу корутина у свим библиотекама, уз одржавање предвидљивих перформанси и контроле.

Еволуција корутина од C до C++

Корутине се налазе у фасцинантној средини између класичних функција и потпуно развијених нити, и њихова прича од C трикова ниског нивоа до стандардизоване подршке за језик C++20 једна је од најзанимљивијих еволуција у модерном системском програмирању. Ако сте икада покушали да жонглирате повратним позивима, машинама стања и синхронизацијом нити само да бисте руковали неблокирајућим улазно-излазним операцијама, већ сте се сусрели са врстом муке коју су корутине дизајниране да ублаже.

У овом чланку ћемо проћи кроз то како су се корутине развиле од ручно израђених C хакова и POSIX контекстуалних API-ја до модела C++20 корутина високог нивоа, без стека, објашњавајући шта је заправо корутина, како се разликује од генератора, нити и влакана, шта значи „стекована“ наспрам „без стекова“ и како C++20 механизми (објекти обећања, корутине рукују, co_await, co_yield, co_return) се заправо понаша „испод хаубе“.

Шта је, заправо, корутина?

царацтеристицас ен профундидад цсхарп
Повезани чланак:
Детаљне C# функције, екосистем и алати

Не постоји јединствена универзално прихваћена формална дефиниција корутине, али литература се слаже око два кључна својства која разликују корутине од обичних потпрограма:

  • Локална држава опстаје упркос суспензијама: Подаци локални за корутину остају задржани између активација, тако да се свака инстанца корутине понаша као објекат са меморијом.
  • Извршење може паузирати, а касније наставити са исте тачке: Када контрола напусти корутину, може се поново ући у њу на тачној тачки суспензије уместо да се увек почиње од врха као код нормалне функције.

Уместо да имају само један, једнократни улаз и излаз попут потпрограма, корутине подржавају вишеструке тачке уласка и изласка током свог животног века, што их чини моћним за изражавање произвођача, потрошача, машина стања, кооперативних распоређивача и асинхроних токова рада у линеарном, читљивом стилу.

Основне димензије дизајна корутина

Системи корутина из стварног света варирају дуж три важне осе које дефинишу како се понашају и колико су експресивни: модел преноса контроле, да ли су корутине вредности прве класе и да ли су стекфулне или нестекфулне.

Прво, механизам преноса контроле одваја асиметричне од симетричних корутина. У асиметричном дизајну, активна корутина може да се врати само свом директном позиваоцу (користећи операцију концептуално сличну yield), а позивалац га наставља касније (операцијом сличном resume). У симетричним дизајнима, корутина може експлицитно пренети контролу на било коју другу корутину, уместо да се увек враћа ономе ко ју је позвао.

Друго, неки језици третирају инстанце корутина као објекте прве класе које можете слободно чувати, размењивати и манипулисати, док други приказују корутине само као синтаксичке конструкције са ограниченим начинима интеракције са њима. Првокласна подршка драматично повећава флексибилност и композибилност.

Треће, корутине могу бити наслагане или ненасложене. Корутина са стеком може да се суспендује дубоко унутар угнежђеног стека позива; када се настави, сваки фрејм у том стеку наставља тамо где је стао. Корутине без стека суспендују се само на нивоу саме функције корутине: редовне помоћне функције не могу да принесу резултат осим ако су саме корутине или посебно анотиране.

Термин „пуна корутина“ је предложен за најизразитију комбинацију: стековану, првокласну корутину, симетричну или асиметричну, што је довољно моћно да изрази једнократне наставке или разграничене наставке. Иако симетрични и асиметрични стилови имају еквивалентну изражајну моћ, асиметрични модел често делује више „рутински“ и познат је већини програмера.

Подпрограми, корутине, генератори и нити

Подпрограми се могу посматрати као посебан случај корутина са озбиљно ограниченим током управљања и понашањем стања. Нормална функција увек почиње од своје прве инструкције, једном се излази и након тога одбацује своје локално стање. Корутина, насупрот томе, може пренети контролу на друге корутине, касније се наставити у тачки приноса и задржати своје стање током ових преноса. Вишеструке инстанце корутина исте функције могу коегзистирати, свака са својим сачуваним локалним подацима.

Генератори чине значајан подскуп корутина, понекад названих „полукорутине“. Као и корутине, генератори могу више пута обуставити извршавање, а касније наставити, али се увек враћају свом директном позиваоцу и немају начина да преусмере извршавање на произвољну трећу корутину. То ограничење је намерно: генератори су оптимизовани за имплементацију итератора и лењих секвенци где сваки yield једноставно значи „произвести вредност за онога ко ме понавља“.

У ствари, можете емулирати опште корутине постављањем диспечера преко система генератора, на пример, тако што би се поставила трамболина највишег нивоа која прима токене од генератора и одлучује који генератор ће следећи активирати. Ова техника је историјски коришћена у језицима попут раних верзија Пајтона, које су имале само генераторе, али не и уграђене корутинске примитиве.

Корутине се често пореде са нитима, али се оне у основи односе на кооперативно заказивање, а не на превентивни паралелизам. Корутине пружају конкурентност у смислу преплитања задатака без промене укупне семантике, али се саме не извршавају истовремено на више језгара. Корутина даје контролу само на експлицитним тачкама обуставе, тако да се код између тих тачака извршава без прекида од стране других корутина.

Овај кооперативни модел елиминише многе главобоље синхронизације уобичајене код нити: Пошто се само једна корутина извршава у датом тренутку на датом распоређивачу, често вам нису потребни мутекси или атомске операције за обично дељено стање. С друге стране, саме корутине неће искористити више језгара процесора осим ако их не комбинујете са нитима или вишенитним извршиоцем.

Класичан пример корутине: произвођач-потрошач

Уџбенички пример симетричних корутина је образац произвођач-потрошач са дељеним редом чекања. Једна корутина генерише ставке и гура их у ред док се не попуни, а затим их предаје потрошачу; потрошач убацује ставке док се ред не испразни, а затим их враћа произвођачу. Извршење се одбија напред-назад како свака страна кооперативно предаје контролу.

У таквој имплементацији, произвођач и потрошач изгледају као да раде „паралелно“ из перспективе програмера, иако се у ствари само пребацују напред-назад унутар једне нити извршавања. Нема потребе за нитима на нивоу оперативног система или прекидачима контекста: операција yield може бити скок ниског нивоа који преусмерава оквир активног стека.

Овај пример се често користи за представљање вишенитног рада, али је важно напоменути да су саме корутине довољне за изражавање логике, и да њихова замена нитима може бити непотребна или чак штетна у окружењима којима су потребне гаранције у реалном времену или минимално оптерећење током извршавања.

Зашто су корутине важне: машине стања, актери и асинхрони токови рада

Пошто корутине задржавају и своју тачку извршења и своје локалне променљиве кроз приносе, Они пружају веома природан начин за имплементацију сложених машина стања без преоптерећења прекидачима, заставицама или експлицитним бројачима програма. Тренутна тачка суспензије буквално представља тренутно стање.

Корутине су такође добро прилагођене моделима конкурентности у стилу актера, као што су они који се користе у многим гејм енџинима. Сваки актер може бити имплементиран као корутина која периодично предаје контролу централном распоређивачу, који покреће једног актера за другим у једној нити. Овај кооперативни мултитаскинг елиминише потребу за већином закључавања, а и даље пружа одзивно понашање.

Генератори изграђени на корутинама су идеални за рад са токовима података и обилажењем структура података, посебно када желите лењу производњу вредности на захтев. Уместо убацивања вредности у потрошача, генератор омогућава потрошачу да извлачи вредности једну по једну користећи једноставну петљу.

Корутине такође блистају у комуникационим обрасцима као што су цевоводи и комуникација секвенцијалних процеса (CSP), где је свака фаза корутина која се враћа када чека на улаз или излаз. Планер затим наставља корутине када су њихови комуникациони канали спремни, пружајући елегантну алтернативу петљама догађаја са великим бројем повратних позива.

Коначно, многе нумеричке библиотеке користе стил који се понекад назива „обрнута комуникација“, где се решавач обуставља кад год му је потребно да корисник пружи неку процену функције, а затим наставља када корисник одговори. Корутине пружају директан и читљив начин изражавања тог тока управљања напред-назад.

Од нисконивоских C имплементација до преносивих библиотека

Једна класа имплементација ручно добија други стек позива, а затим га користи setjmp/longjmp да прелазите између корутина. Платформски специфичан инлине асемблер може подесити нови стек за сваку корутину; на POSIX системима, сигнали комбиновани са sigaltstack може се користити за покретање извршавања на алтернативном стеку у чистом C-у. Када свака корутина има свој стек, setjmp чува стање процесора и показивач стека, и longjmp враћа их да би наставио корутину.

Неке POSIX и UNIX-усклађене C библиотеке су историјски излагале помоћне функције као што су getcontext, setcontext, makecontext swapcontext, који директно обухватају идеју пребацивања између контекста на нивоу корисника. Иако су они од тада означени као застарели у POSIX.1-2008, они су чинили окосницу неколико библиотека корутина и инспирисали касније дизајне.

Заобилажење минималних имплементација корутина setjmp/longjmp и контекстне API-је у потпуности, уместо тога, одабирујући ручно писани асемблер који само замењује бројач програма и показивач стека, бришући остале регистре. Ово може бити драматично брже на неким ABI-јима јер чува тачно оно што је потребно и ништа више, док setjmp мора конзервативно да складишти већи скуп регистара.

Да би се сакрила сва ова сложеност од кода апликације, током година се појавило више C библиотека које пакују корутине пребацујући их у чисте API-је, као што је Рас Коксов libtask и разни други (libpcl, coro, lthread, libcoro, libaco, libco и више). Ове библиотеке обично пружају апстракције попут лаких задатака или влакана која се могу наставити и вратити без потребе да позивалац брине о основним триковима асемблирања.

Приближне корутине у C-у коришћењем макроа

Тамо где одвојени стекови или API-ји за пребацивање контекста нису доступни или пожељни, програмери су такође апроксимирали корутине у чистом C-у помоћу макроа и switch наредби, техника коју је чувено документовао Сајмон Тејтам и која је повезана са класичним триком „Дафовог уређаја“.

Основна идеја је да се стање корутине кодира као бројач програма имплементиран са switch case етикете, где сваки yield-слични макро се проширује у код који бележи тренутну ознаку у статичкој променљивој, а затим се враћа позиваоцу. При следећем позиву, функција се враћа на ту ознаку уместо да почиње од почетка.

Библиотеке попут Protothreads-а надограђују се на овај образац како би обезбедиле изузетно лагане корутине без стека које се уклапају у ограничена уграђена окружења, Али овај приступ долази са озбиљним ограничењима: локалне променљиве се природно не задржавају кроз yields осим ако нису сачуване у статичким или екстерним структурама, не можете лако суспендовати из угнежђених позива функција и генерално имате само једну улазну тачку.

Чак и његови заговорници описују ову макро-базирану превару као један од најружнијих C кодова икада коришћених у продукцији, а критичари истичу да резултујући ток контроле може бити тешко промишљати и одржавати током времена. Ипак, он остаје користан компромис у системима где додатни стекови или трикови са линкерима нису могући.

Одскочна даска: влакна, нити и сродне апстракције

У мејнстрим окружењима којима недостају изворне корутине, нити (и, у мањој мери, влакна) су постале подразумевани градивни блок за конкурентност, чак и када би кооперативно понашање било довољно. Нити су често добро подржане и добро документоване, али решавају шири и сложенији проблем него што је то заправо потребно већини случајева употребе корутина.

Влакна, где су доступна, представљају ближе поклапање са корутинама на нивоу корисника јер су кооперативно заказана и могу се мењати без учешћа оперативног система, што их чини природном подлогом на којој се имплементирају API-ји у стилу корутина. Међутим, системска подршка за влакна је неуједначена у поређењу са нитима, а преносивост пати.

Једна значајна разлика између нити и корутина је понашање при заказивању. Нити се обично претходе на произвољним тачкама, што приморава програмере да размишљају о условима трке и синхронизацији свуда. Корутине, насупрот томе, мењају контролу само на експлицитним тачкама суспензије, што вам често омогућава да пишете једноставнији код без закључавања или атомских операција.

Језици и времена извршавања су истражили многе путеве за емулацију корутина преко постојеће инфраструктуре, од преписивања бајткода (као у неким Јава корутинским оквирима) до мапирања корутинских конструкција на итераторе (као што је C# урадио са yield пре async/await) или њиховом изградњом преко зелених нити, наставака или влакана.

Корутине у различитим програмским језицима

Током деценија, многи језици су експериментисали са конструкцијама сличним корутинама, свака са својим укусом и компромисима, и разумевање овог екосистема помаже да се еволуција C++ стави у контекст.

Неки језици нуде првокласне, стекасте корутине директно у библиотеци извршавања и стандардној библиотеци. Луа, на пример, подржава асиметричне, стекасте корутине од верзије 5.0 преко свог стандарда coroutine API, са примитивима за креирање, настављање и принос. Модула-2 је историјски укључивала подршку за корутине кроз процедуре као што су NEWPROCESS TRANSFER који постављају одвојене стекове и пребацују се између контекста.

Други екосистеми су градили корутине преко постојећих примитива попут наставка или зелених нити. Racket (и Scheme дијалекти уопште) могу имплементирати корутине готово тривијално јер откривају наставке као вредности прве класе. Smalltalk системи, где су стекови за извршење манипулативни објекти, могу слично хостовати апстракције корутина без додатне подршке за виртуелну машину. У OCaml-у, кооперативна конкурентност је обезбеђена путем модула који превентивно заказују нити на једној ОС нити, док новије верзије додају подршку за стил зелених нити.

Језици фокусирани на асинхроно програмирање често су почињали са генераторима пре него што су увели потпуне корутине. C# је првобитно додао генераторе кроз yield и образац итератора, који се затим развио у async/await да моделира асинхроне операције као корутине. JavaScript је следио сличан пут: ES2015 је увео генераторе као посебан случај корутина, а касније верзије су додале async/await изграђено на основу обећања и генератора.

У JVM свету, сама Јава не нуди изворне корутине, али алати и језици око ње попуњавају празнину. Неке библиотеке модификују бајткод да би симулирале понашање корутина, друге користе JNI за приступ механизмима специфичним за платформу, а неке се ослањају на нити да би емулирале семантику корутина по вишој цени. Kotlin, с друге стране, пружа корутине као функцију библиотеке прве стране и може да интероперабилно сарађује са Јава кодом (иако Јава не може природно да „суспендује“ и уместо тога мора да блокира или користи фјучерсе).

Скриптни и динамички језици су усвојили различите приступе. Пајтон је почео са побољшаним генераторима (PEP 342), проширио их је делегирањем подгенератора (PEP 380), и на крају је увео експлицитне изворне корутине са async/await (PEP 492), касније резервишући те кључне речи у Пајтону 3.7. Руби имплементира понашање слично корутинама путем влакана; Раку и Tcl нуде изворне корутинске конструкције; PHP 8.1 је додао влакна за подршку библиотекама заснованим на корутинама за асинхрони улаз/излаз.

Системски оријентисани језици такође истражују моделе сличне корутинама са својим посебним преокретом. Go користи горутине — лагане, мултиплексиране процесе са динамички димензионисаним стековима. Иако горутине нису корутине у строгом смислу (ближе су зеленим нитима, а локални подаци не преживљавају вишеструке „позиве“ у смислу корутина), оне заузимају сличан ментални простор као задаци на нивоу корисника којима управља распоређивач времена извршавања. D излаже корутине путем Fiber у својој стандардној библиотеци, а неки фрејмворци их умотавају у практичне интерфејсе у стилу генератора.

Уведите C++: библиотеке пре стандарда

Пре него што је C++ стандардизовао корутине, екосистем се ослањао на библиотеке трећих страна како би семантику корутина унео у језик, користећи мешавину пребацивања контекста асемблера, платформских API-ја и паметног метапрограмирања шаблона.

Boost.Context се појавио као нисконивоска основа за пребацивање контекста извршавања између више архитектура и оперативних система, пружајући преносив начин за манипулацију стековима корисничког простора. Поред тога, Boost.Coroutine, а касније и Boost.Coroutine2, нудили су апстракције корутина вишег нивоа, прелазећи са подршке за симетричне и асиметричне облике на модернији асиметрични интерфејс који се боље усклађује са савременим C++ идиомима.

Други пројекти су истраживали различите углове, као што су корутине без стека засноване на претпроцесору које емулирају await/yield семантика, библиотеке са једним заглављем које обавијају платформска влакна или оквире (као што су корутине Mordor или Oat++) који се посебно фокусирају на скривање асинхроних повратних позива улазно/излазних операција иза секвенцијалног кода сличног корутинама.

Ови екосистеми су показали да су C++ програмери били жељни изражајности сличне корутинама, али су такође открили болне тачке ад-хок решења: недоследну синтаксу, тешке проблеме преносивости, неспретно дебаговање и алате и нетривијалну интеграцију са остатком стандардне библиотеке.

C++20 корутине: стандардизовани модел без стека

C++20 је коначно увео корутине у језик као првокласну карактеристику, али са намерно минималистичким и нисконивоским дизајном. Уместо да у стандардну библиотеку убаци специфичну апстракцију високог нивоа (као што су „задатак“, „генератор“ или „будућност“), C++20 је стандардизовао градивне блокове који омогућавају библиотекама да дефинишу сопствене типове прилагођене корутинама.

Функција постаје корутина ако њено тело садржи било коју од конструкција специфичних за корутину: , co_await оператор да обустави док неки догађај или вредност не буду спремни, co_yield израз за производњу вредности и суспендовање (као код генератора), или co_return израз за довршавање корутине, опционо са резултатом.

Када компајлер детектује било коју од ових конструкција, он трансформише функцију у машину стања чије је перзистентно стање сачувано у „корутинском оквиру“ додељеном хипу. осим ако оптимизација не докаже да је животни век оквира строго угнежђен унутар позиваоца и да се може уградити у оквир стека позиваоца. Овај оквир садржи објекат обећања, копије аргумената, локалне променљиве које постоје у тачкама обуставе и метаподатке књиговодства за наставак извршавања.

Кључно је да C++20 корутине немају стек: Корутина може да обустави рад само на експлицитним тачкама обуставе (co_await or co_yield) и не могу транспарентно да се одрже из произвољних угнежђених позива, осим ако те функције нису такође корутине или на други начин не учествују у механизму корутина. Ово чини имплементацију једноставнијом и предвидљивијом, по цену извесне изражајне моћи у поређењу са потпуно стекованим дизајном.

Ограничења и животни циклус C++20 корутине

Није свака функција у C++20 дозвољено да буде корутина; стандард намеће неколико ограничења како би модел остао разуман. Корутине не могу бити constexpr or consteval функције, оне не могу бити конструктори, деструктори или main функцију и не смеју да користе варијабилне аргументе у C стилу или типове повратних вредности као што је обични auto без додатне спецификације.

Када се корутина први пут позове, она се не понаша одмах као нормално тело функције. Уместо тога, пролог који генерише компајлер додељује оквир корутине (обично путем operator new), копира параметре функције у тај оквир (по вредности или по референци како је декларисано), конструише обећани објекат, а затим позива promise.get_return_object(), што обично даје неки дескриптор или омотачки објекат који се враћа позиваоцу.

Обећани објекат је кориснички дефинисан тип, откривен путем std::coroutine_traits на основу типа повратка и листе параметара корутине, и диктира како функционишу резултати, изузеци и политике суспензије. Компилатор закључује Promise откуцајте, а затим позовите методе као што је initial_suspend(), final_suspend(), return_value() or return_void(), i unhandled_exception() у одговарајућим фазама животног циклуса корутине.

На почетку извршавања, корутина позива promise.initial_suspend() co_awaitшта год да се врати, што омогућава ауторима библиотека да одлуче да ли је тип њихове корутине „нестрпљив“ (почиње одмах да се извршава) или „лењ“ (враћа се позиваоцу док се експлицитно не настави). Када се корутина коначно заврши путем co_return или необрађени изузетак, он позива promise.final_suspend(), дајући библиотеци последњу шансу да закаже наставак или среди.

Када се оквир корутине уништи — било након завршетка или путем експлицитне операције уништавања на његовом дескриптору — Извршно окружење уништава обећани објекат, копије параметара и све преостале активне локалне вредности, а затим ослобађа меморију помоћу operator delete (или са алокатором специфичним за обећање, ако је дат). Ако алокација не успе и обећање дефинише get_return_object_on_allocation_failure(), корутина може грациозно сигнализирати неуспех без избацивања std::bad_alloc.

co_await, очекивани и чекачи

co_await оператор је примарни примитив за суспензију у систему корутина у C++20, и разумевање његове механике је кључно за дизајнирање робусних асинхроних апстракција.

Кад пишете co_await expr; унутар корутине, компајлер прво конвертује expr у „очекивани“ објекат, било пропуштањем кроз promise.await_transform(expr) ако такав члан постоји, или коришћењем таквог каквог јесте. Затим одређује објекат „waiter“ позивањем члана operator co_await на очекиваном, нечлан operator co_await, или једноставно третирање самог awaitable-а као awaiter-а ако такав оператор не постоји.

Чекач мора да обезбеди три кључне операције: await_ready(), await_suspend(handle) await_resume(). If await_ready() враћа тачно, корутина се не суспендује и директно позива await_resume(), омогућавајући брзе путање за већ завршене операције. Ако врати вредност false, корутина се суспендује, њено стање се чува у оквиру и await_suspend() се позива са идентификатором тренутне корутине.

Унутра await_suspend(), чекач може да одлучи шта да ради са ручком корутине: закажите његово касније настављање на неком извршиоцу, наставите другу корутину или чак одмах наставите исту корутину (у зависности од типа повратка и вредности await_suspend()). Када се очекивана операција заврши, неко коначно позове handle.resume(), у ком тренутку се контрола враћа на непосредно пре await_resume(), А затим await_resume() даје резултат co_await израз.

Стандардна библиотека испоручује два тривијална awaitable-а: std::suspend_always std::suspend_never, који се често користе у initial_suspend() final_suspend() имплементације за означавање лењег или жељног почетка и како се понашати на крају. Софистициранији чекачи могу да држе стање по операцији, на пример да би повезали корутине са асинхроним I/O API-јима, а то стање се налази унутар оквира корутине преко тачке суспензије.

co_yield и корутине у стилу генератора

co_yield израз се надовезује на co_await да подржи понашање слично генератору, где корутина више пута производи вредности за позиваоца који итерира преко њих.

Концептуално, co_yield value; проширује се у позив ка promise.yield_value(value) а затим суспензија, обично преко co_await std::suspend_always или сличан awaitable. Имплементација обећања је одговорна за чување добијене вредности на неком приступачном месту (копирањем, премештањем или референцирањем) тако да корисник може да је преузме пре него што се корутина настави.

Библиотечки код који имплементира генераторе обично дефинише тип обећања који открива методе за приступ тренутно добијеној вредности и за интеграцију са стандардним протоколима итерације, као што је пружање begin()/end() на омотачу ручке и унапређење основне корутине при сваком инкременту.

Обрада грешака, висеће референце и суптилни детаљи

C++20 корутине се интегришу са обрадом изузетака C++ путем обећања unhandled_exception() метод, коју компајлер позива ако изузетак напусти тело корутине. Корутина затим наставља ка свом коначном суспензији, а очекује се да ће обећање организовати да се грешка саопшти ономе ко је власник типа резултата корутине.

Пошто се параметри копирају или референцирају у оквир корутине у време креирања, Мора се водити рачуна о параметрима референци: ако се они односе на објекте чији се животни век завршава пре него што се корутина настави, корутина може дереференцирати висеће референце. Ово није проблем специфичан за корутину, али перзистентна природа оквира олакшава случајно надживевање референцираних објеката.

Стандард се такође развио како би се разјаснили гранични случајеви путем извештаја о дефектима, као што је проглашење одређених неважећим return_void подешава лоше обликоване уместо да производи недефинисано понашање када се испадне са краја корутине и омогућава co_await у више контекста као што су ламбда тела.

Заједно, ова правила и усавршавања обликују релативно нисконивоски, али предвидљив модел на којем библиотеке корутина вишег нивоа могу безбедно да граде, од једноставних генератора до потпуних система асинхроних/чекајућих задатака интегрисаних са извршиоцима и распоређивачима.

Гледано одозго, путовање од ad-hoc асемблерских трикова у C-у до структурираних, без стекова, обећањима вођених корутина C++20 одражава сталан потез ка безбеднијим, композибилнијим апстракцијама за изражавање сложеног тока управљања, асинхроних операција и стањастих израчунавања, без одустајања од перформанси и контроле на које се системски програмери ослањају.

Релатед постс: