8.1. Kooperativní souběžnost

Plánovací model asyncio je kooperativní, nikoli preemptivní. Toto rozlišení je tím nejdůležitějším myšlenkovým modelem, na kterém staví zbytek této sekce, takže se vyplatí jej ujasnit dříve, než se objeví jakýkoli kód.

8.1.1. Preemptivní vs kooperativní

Preemptivní plánovač — typ, který používá desktopový operační systém pro souběžný běh mnoha programů — může pozastavit kteroukoli právě běžící část kódu v jakémkoli okamžiku a přepnout na jinou. Běžící kód nemusí dělat nic zvláštního; plánovač jej přeruší. To činí preemptivní plánování velmi flexibilním (žádná část kódu nemůže svou pomalostí připravit ostatní o čas), ale zároveň to znamená, že každou sdílenou proměnnou je nutné pečlivě chránit, protože k přepnutí může dojít kdekoli — třeba i v polovině zápisu hodnoty nebo uprostřed čtení seznamu.

Kooperativní plánovač může mezi částmi kódu přepínat pouze v bodech, kde právě běžící část explicitně předá řízení zpět. V asyncio jsou těmito body každý await a každé volání korutiny, která interně předává řízení (nejčastěji asyncio.sleep()). Mezi dvěma awaity má běžící korutina procesor zcela pro sebe.

Z toho plynou dva důsledky:

  • Korutina, která nikdy neawaituje, se nikdy nepozastaví. Pokud korutina uvízne v těsné smyčce bez await uvnitř, monopolizuje plánovač a nic jiného nepostupuje. Řešením je na vhodném místě ve smyčce zavolat await asyncio.sleep_ms(0) (nebo jiné čekací volání).

  • Sdílený stav je mezi awaity bezpečný. Dvě korutiny se nemohou prokládat uprostřed operace, která v sobě nemá žádný await. Druh poškození, který vzniká, když preempce přistane uprostřed vícekrokové aktualizace — jedna část kódu čte hodnotu, zatímco jiná je v polovině její změny — zde prostě nemůže nastat. Koordinace mezi korutinami je stále potřeba, když si jich několik musí sdílet zdroj napříč awaity, ale problém prokládání uprostřed řádku se neuplatňuje.

8.1.2. Tři vrstvy

Každý asyncio skript je postaven ze stejných tří vrstev. Následující dvě stránky je popisují podrobně; toto jsou označení, která je dobré mít na paměti při jejich čtení.

  • Korutiny — funkce deklarované pomocí async def, z nichž každá je samostatnou jednotkou práce, která awaituje tam, kde je to vhodné. Přehled jazyka Python představil klíčová slova async/await; v asyncio jsou způsobem, jakým korutina předává řízení zpět plánovači.

  • Úlohy — obal, který asyncio.create_task() umístí kolem korutiny, aby ji naplánoval souběžně se současnou. Aplikace obvykle vytvoří hrstku úloh pro dlouho běžící práce (smyčka snímání, síťový klient, čtečka UART, …).

  • Smyčka událostí — engine pod tím vším, který sleduje, které korutiny čekají a které jsou připraveny k běhu, a při každém await přepíná mezi úlohami. Aplikace smyčku nepíše; předá vrcholovou korutinu funkci asyncio.run() a smyčka odtud řídí vše ostatní.

Když je aplikace popsána tímto způsobem — jako malá množina korutin složená smyčkou událostí — souběžnost se stává vlastností tvaru programu, nikoli něčím, co musí aplikace řídit krok za krokem.