8.1. Kooperatív konkurencia¶
Az asyncio ütemezési modellje kooperatív, nem preemptív. Ez a megkülönböztetés a legfontosabb gondolati modell, amelyre a fejezet többi része épül, ezért érdemes tisztázni, mielőtt bármilyen kód megjelenne.
8.1.1. Preemptív kontra kooperatív¶
Egy preemptív ütemező — amilyet egy asztali operációs rendszer használ, hogy egyszerre sok programot futtasson — bármelyik pillanatban szüneteltetheti az éppen futó kódot, és átválthat egy másikra. A futó kódnak nem kell semmi különöset tennie; az ütemező megszakítja. Ez nagyon rugalmassá teszi a preemptív ütemezést (egyetlen kódrészlet sem éheztetheti ki a többit azzal, hogy lassú), de azt is jelenti, hogy minden megosztott változót gondosan védeni kell, mert a váltás bárhol bekövetkezhet — akár egy érték írásának felénél, akár egy lista olvasásának közepén.
Egy kooperatív ütemező csak olyan pontokon válthat a kódrészletek között, ahol az éppen futó részlet kifejezetten visszaadja a vezérlést. Az asyncio esetében ezek a pontok minden await, és minden olyan korutin hívása, amely belsőleg ad át vezérlést (leggyakrabban az asyncio.sleep()). Két await között a futó korutiné a CPU.
Ebből két következmény adódik:
Egy korutin, amely soha nem await-el, soha nem áll meg. Ha egy korutin egy szoros ciklusban marad
awaitnélkül, akkor lefoglalja az ütemezőt, és semmi más nem halad előre. A megoldás az, hogy a ciklus egy értelmes pontján meghívunk egyawait asyncio.sleep_ms(0)(vagy más várakozó) hívást.A megosztott állapot biztonságos az await-ek között. Két korutin nem fonódhat össze egy olyan művelet felénél, amelyben nincs
await. Az a fajta sérülés, amely akkor merül fel, amikor a preempció egy többlépéses frissítés közepén csap le — az egyik kódrészlet egy értéket olvas, miközben egy másik éppen a felénél tart a megváltoztatásnak — itt egyszerűen nem fordulhat elő. A korutinok közötti összehangolásra továbbra is szükség van, amikor többüknek meg kell osztaniuk egy erőforrást az await-eken átívelően, de a sor közepén történő összefonódás problémája itt nem áll fenn.
8.1.2. A három réteg¶
Minden asyncio szkript ugyanabból a három rétegből épül fel. A következő két oldal részletesen tárgyalja őket; ezek a címkék érdemes szem előtt tartani olvasás közben.
Korutinok —
async def-fel deklarált függvények, amelyek mindegyike egy önálló munkaegység, és ott await-el, ahol szükséges. A Python áttekintő bemutatta azasync/awaitkulcsszavakat; az asyncio-ban ezek révén ad vissza vezérlést egy korutin az ütemezőnek.Feladatok — egy burkoló, amelyet az
asyncio.create_task()helyez egy korutin köré, hogy az aktuálissal párhuzamosan ütemezze. Az alkalmazás jellemzően néhány feladatot hoz létre a hosszan futó munkákhoz (a pillanatkép-ciklus, a hálózati kliens, a UART-olvasó, …).Az eseményhurok — a motor a háttérben, amely nyilvántartja, hogy mely korutinok várakoznak és melyek készek a futásra, minden
await-nél váltva a feladatok között. Az alkalmazás nem írja meg a hurkot; átad egy felső szintű korutint azasyncio.run()függvénynek, és a hurok onnantól mindent mozgat.
Amikor az alkalmazást így írjuk le — mint egy kis korutinhalmazt, amelyet egy eseményhurok fűz össze —, a konkurencia a program alakjának tulajdonságává válik, nem pedig olyasmivé, amit az alkalmazásnak lépésről lépésre kell kezelnie.