Souběžné a paralelně běžící úlohy naprogramované v Pythonu – knihovna Trio

10. 5. 2022
Doba čtení: 32 minut

Sdílet

 Autor: Depositphotos
Dnes si popíšeme koncepty knihovny Trio. Ta je postavená nad relativní novinkou v Pythonu: klíčovými slovy async a await. Cílem Tria je zjednodušit tvorbu aplikací, v nichž jednotlivé části mohou běžet souběžně.

Obsah

1. Souběžné a paralelně běžící úlohy naprogramované v Pythonu – knihovna Trio

2. Spuštění korutiny v knihovnách Curio a Trio

3. Předání parametrů synchronně či asynchronně volané korutině

4. Chování programu při spuštění několika korutin funkcí trio.run

5. Asynchronní spuštění korutin v knihovně curio

6. „Strukturované programování souběžných úloh“

7. Spuštění korutin s čekáním na jejich dokončení v bloku async with

8. Postupné spouštění korutin ve více blocích async with

9. Výjimky vznikající v korutinách

10. Zachycení výjimek vyhazovaných z korutin

11. Vznik výjimek souběžně v několika korutinách

12. Je počet korutin (souběžných úloh) prakticky omezen?

13. Paměťové nároky programu s 10000 korutinami

14. Spuštění 10000 souběžných úloh se sledováním vytížení CPU

15. Komunikace mezi souběžnými úlohami s využitím kanálů

16. Základní vlastnosti kanálů nabízených knihovnou Trio

17. Ukázka klasické úlohy typu producent-konzument

18. Obsah druhé části článku

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Souběžné a paralelně běžící úlohy naprogramované v Pythonu – knihovna Trio

Podobný koncept práce s korutinami, který jsme viděli v knihovně Curio v předchozím článku, je implementován i v knihovně nazvané Trio, která z Curia částečně vychází. Cílem této knihovny je nejenom nabídnout programátorům souběžný běh korutin (a plně paralelní běh I/O operací – což je úkol pro operační systém), ale navíc i zajistit korektnost výsledného programu, což vůbec není jednoduchý úkol. Podrobnější informace o této velmi užitečné knihovně si řekneme v navazujících kapitolách.

Autor knihovny Trio je velkým zastáncem metodiky strukturovaného programování souběžných úloh. Při vysvětlování svého přístupu se vrací do doby, kdy byly programy (například ve Fortranu a BASICu) tvořeny nestrukturovaným kódem plným příkazů goto. Takový kód nebyl příliš přehledný a navíc možnost „skoku kamkoli“ mnohdy vedla k jeho chybnému chování. Problematika byla vyřešena zavedením strukturovaných konstrukcí – procedur, podmínek a smyček (což nutně nemusí vést k zavedení nových příkazů do jazyka, spíše se jedná o návrhové vzory). Podobná situace (podle autora Tria) vládne v oblasti souběžných úloh, kdy se například příkazem go spouští gorutiny zcela nekoordinovaně a kdekoli; navíc není zaručeno jejich ukončení ani zachycení případných chyb. Namísto takto nestrukturovaného přístupu je v Triu použit blok async with popř. async for, což si ukážeme na demonstračních příkladech.

2. Spuštění korutiny v knihovnách Curio a Trio

V předchozím článku jsme si ukázali způsob spuštění korutiny z hlavní funkce (main). Korutina je reprezentována funkcí s deklarací async a nelze ji tedy spustit přímo. Proto je nutné (v případě použití knihovny Curio popsané minule) použít funkci nazvanou run. Povšimněte si, že prvním (a jediným povinným) parametrem této funkce je korutina, která se má spustit. To mj. znamená, že se můžeme obejít bez explicitního vytvoření úlohy a jejího následného volání:

import curio
 
 
async def task():
    print("task started")
    await curio.sleep(5)
    print("task finished")
 
 
def main():
    print("main started")
    curio.run(task)
    print("done")
 
 
main()

Prakticky stejným způsobem bude problematika spuštění korutiny realizována v knihovně Trio. Oba příklady jsou prakticky totožné, liší se pouze odlišným importem a jiným jménem balíčku s volanou funkcí run:

import trio
 
 
async def task():
    print("task started")
    await trio.sleep(5)
    print("task finished")
 
 
def main():
    print("main started")
    trio.run(task)
    print("done")
 
 
main()
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio01.py.

Povšimněte si však situace, kdy v korutině task zavoláme korutinu trio.sleep přímo, tedy bez použití klíčového slova await. To je obecně nekorektní řešení:

import trio
 
 
async def task():
    print("task started")
    trio.sleep(5)
    print("task finished")
 
 
def main():
    print("main started")
    trio.run(task)
    print("done")
 
 
main()

Na tento problém nás knihovna Trio upozorní varováním (a toto varování je vhodné ihned opravit):

main started
task started
trio_01_error.py:6: RuntimeWarning: coroutine 'sleep' was never awaited
  trio.sleep(5)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
task finished
done
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio01_error.py.

3. Předání parametrů synchronně či asynchronně volané korutině

Korutinám je v naprosté většině případů nutné předávat nějaké parametry. V případě, že je použita knihovna trio, jsou parametry korutině předány přímo v rámci funkce trio.run. Povšimněte si podstatného rozdílu – nevoláme zde přímo korutinu task (ve skutečnosti ani v případě asyncio nedochází k jejímu přímému volání), ale reference na (synchronně či asynchronně) volanou korutinu se společně s jejími parametry předává funkci run:

import trio
 
 
async def task(n, s):
    print("task started")
 
    for i in range(n):
        print(f"{i+1}/{n}")
        await trio.sleep(s)
 
    print("task finished")
 
 
def main():
    print("main started")
    trio.run(task, 10, 1)
    print("done")
 
 
main()

Po spuštění tohoto příkladu uvidíme, že se díky await vždy čeká na dokončení předchozí korutiny:

main started
task started
1/10
2/10
3/10
4/10
5/10
6/10
7/10
8/10
9/10
10/10
task finished
done

4. Chování programu při spuštění několika korutin funkcí trio.run

Zkusme si nyní spustit následující skript, v němž jsou vytvořeny a spuštěny celkem tři korutiny. Varianta určená pro knihovnu curie vypadá takto (viz též předchozí článek):

import curio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await curio.sleep(s)
 
    print(f"{name} task finished")
 
 
def main():
    print("main started")
    curio.run(task, "1st", 10, 1)
    curio.run(task, "2nd", 10, 1)
    curio.run(task, "3rd", 10, 1)
    print("done")
 
 
main()

Prakticky stejným způsobem lze realizovat tentýž skript, nyní ovšem s využitím knihovny Trio:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
def main():
    print("main started")
    trio.run(task, "1st", 10, 1)
    trio.run(task, "2nd", 10, 1)
    trio.run(task, "3rd", 10, 1)
    print("done")
 
 
main()

Po spuštění tohoto skriptu získáme následující výstup, který jasně ukazuje, že i přes volání await trio.sleep v korutině nedojde k přepnutí na další korutinu – další korutina totiž ještě ani nebyla vytvořena. To vlastně znamená, že trio.run zajišťuje synchronní volání korutiny (protože se skutečně jedná o korutinu získanou transformací funkce s využitím konstrukce async):

main started
1st task started
1st 1/10
1st 2/10
1st 3/10
1st 4/10
1st 5/10
1st 6/10
1st 7/10
1st 8/10
1st 9/10
1st 10/10
1st task finished
2nd task started
...
...
...
3rd task started
3rd 1/10
3rd 2/10
3rd 3/10
3rd 4/10
3rd 5/10
3rd 6/10
3rd 7/10
3rd 8/10
3rd 9/10
3rd 10/10
3rd task finished
done

5. Asynchronní spuštění korutin v knihovně curio

Pro skutečné asynchronní spouštění korutin se v knihovně curio používá funkce spawn. Knihovna trio sice programátorům nabízí jiné (lepší) řešení, ovšem pro porovnání si nejdříve ukažme, jak lze spustit tři korutiny s využitím možností nabízených knihovnou curio:

import curio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await curio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
 
    task1 = await curio.spawn(task, "1st", 10, 1)
    task2 = await curio.spawn(task, "2nd", 10, 1)
    task3 = await curio.spawn(task, "3rd", 10, 1)
 
    await task1.join()
    await task2.join()
    await task3.join()
 
    print("done")
 
 
curio.run(main())
Poznámka: můžeme zde vidět kombinaci await curio.spawn a await task.join().

Nyní dojde ke skutečnému souběžnému běhu korutin, mezi nimiž se přepíná během „spánku“ vyvolanému zavoláním curio.sleep:

main started
1st task started
1st 1/10
2nd task started
2nd 1/10
3rd task started
3rd 1/10
1st 2/10
2nd 2/10
3rd 2/10
1st 3/10
2nd 3/10
3rd 3/10
1st 4/10
2nd 4/10
3rd 4/10
1st 5/10
2nd 5/10
3rd 5/10
1st 6/10
2nd 6/10
3rd 6/10
1st 7/10
2nd 7/10
3rd 7/10
1st 8/10
2nd 8/10
3rd 8/10
1st 9/10
2nd 9/10
3rd 9/10
1st 10/10
2nd 10/10
3rd 10/10
1st task finished
2nd task finished
3rd task finished
done

6. „Strukturované programování souběžných úloh“

Knihovna Trio je z pohledu programátora-uživatele do značné míry postavena na bloku async with, jenž vychází z klasického bloku with. Jedná se o velmi důležitou součást jazyka Python, protože umožňuje čitelně zapsat několik idiomů. Jedním z těchto idiomů je používání správců kontextu s využitím bloků with pro ty prostředky, které se mají automaticky uzavírat po odchodu z bloku with (a to jakýmkoli způsobem, včetně výskoku z funkce atd.).

Klasickým příkladem je otevření souboru s tím, že jeho uzavření je provedeno automaticky po opuštění bloku with (a to jakýmkoli způsobem, tedy i vyhozením výjimky):

with open('hello.txt', 'w') as fout:
    fout.write('Hi there!')

Interně je funkcionalita bloku with založena na správcích kontextu. V praxi to znamená, že pokud nějaká třída implementuje dvě speciální metody __enter__ a __exit__ nezbytné pro to, aby se jednalo o korektně naprogramované správce kontextu (context manager), je možné ji použít v bloku with:

class Context():
    def __init__(self):
        print("Context: init")
 
    def __enter__(self):
        print("Context: enter")
        return "foo"
 
    def __exit__(self, type, value, traceback):
        print("Context: exit", type, value, traceback)
 
 
print("Before with block")
 
with Context() as c:
    print("Inside with block")
    print(c)
 
print("After with block")

Chování po spuštění ukazuje volání obou speciálních metod:

Before with block
Context: init
Context: enter
Inside with block
foo
Context: exit None None None
After with block

V souvislosti s podporou asynchronního programování byl do Pythonu přidán i blok async with, který pro správce kontextu vyžaduje implementaci speciálních metod __aenter__ a __aexit__:

import trio
 
 
class AsyncContext():
    def __init__(self):
        print("Context: init")
 
    def __aenter__(self):
        print("Context: aenter")
        return trio.sleep(2)
 
    def __aexit__(self, type, value, traceback):
        print("Context: aexit", type, value, traceback)
        return self
 
    def __await__(self):
        print("Context: await")
        return None
 
 
async def main():
    print("Before with block")
 
    async with AsyncContext() as c:
        print("Inside with block")
        print(c)
 
    print("After with block")
 
 
trio.run(main)

A právě blok async with bude použit i ve všech dalších demonstračních příkladech.

7. Spuštění korutin s čekáním na jejich dokončení v bloku async with

Namísto spawn a join se v knihovně Trio používá blok async with se správcem kontextu nazvaným „nursery“. To je přiléhavé jméno, protože se v tomto bloku vytváří „děti–korutiny“ a správce kontextu se stará o jejich sledování a správu. V následujícím příkladu jsou vytvořeny a spuštěny tři korutiny a před opuštěním bloku async with se čeká na jejich dokončení:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task, "1st", 10, 1)
        nursery.start_soon(task, "2nd", 10, 1)
        nursery.start_soon(task, "3rd", 10, 1)
    print("done")
 
 
trio.run(main)

Přesvědčme se o tom, že korutiny běží souběžně:

main started
3rd task started
3rd 1/10
2nd task started
2nd 1/10
1st task started
1st 1/10
3rd 2/10
2nd 2/10
1st 2/10
1st 3/10
2nd 3/10
3rd 3/10
1st 4/10
2nd 4/10
3rd 4/10
1st 5/10
2nd 5/10
3rd 5/10
3rd 6/10
2nd 6/10
1st 6/10
1st 7/10
2nd 7/10
3rd 7/10
3rd 8/10
2nd 8/10
1st 8/10
3rd 9/10
2nd 9/10
1st 9/10
3rd 10/10
2nd 10/10
1st 10/10
1st task finished
2nd task finished
3rd task finished
done

Po spuštění korutiny se nevrací žádná hodnota, tedy ani žádná future atd.:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        print(nursery.start_soon(task, "1st", 10, 1))
        print(nursery.start_soon(task, "2nd", 10, 1))
        print(nursery.start_soon(task, "3rd", 10, 1))
    print("done")
 
 
trio.run(main)

Povšimněte si první trojice hodnot None:

main started
None
None
None
1st task started
1st 1/10
2nd task started
2nd 1/10
3rd task started
3rd 1/10
1st 2/10
2nd 2/10
3rd 2/10
1st 3/10
2nd 3/10
3rd 3/10
1st 4/10
2nd 4/10
3rd 4/10
3rd 5/10
2nd 5/10
1st 5/10
1st 6/10
2nd 6/10
3rd 6/10
1st 7/10
2nd 7/10
3rd 7/10
3rd 8/10
2nd 8/10
1st 8/10
1st 9/10
2nd 9/10
3rd 9/10
3rd 10/10
2nd 10/10
1st 10/10
3rd task finished
2nd task finished
1st task finished
done

8. Postupné spouštění korutin ve více blocích async with

Pokud se mají korutiny spustit postupně, je nutné použít více bloků async with nebo přímo await (což je kratší):

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 

async def main():
    print("main started")
 
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task, "1st", 10, 1)
 
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task, "2nd", 10, 1)
 
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task, "3rd", 10, 1)
 
    print("done")
 
 
trio.run(main)

Nyní se bude program chovat odlišně:

main started
1st task started
1st 1/10
1st 2/10
1st 3/10
1st 4/10
1st 5/10
...
...
...
1st 10/10
1st task finished
2nd task started
2nd 1/10
2nd 2/10
...
...
...
2nd 9/10
2nd 10/10
2nd task finished
3rd task started
3rd 1/10
...
...
...
3rd 9/10
3rd 10/10
3rd task finished
done

9. Výjimky vznikající v korutinách

V korutině, ostatně stejně jako v jakékoli jiné části kódu, může vzniknout výjimka. Ta může být zachycena kódem, který korutinu vytvořil, a to na přesně známém místě – díky existenci bloku async with. Podívejme se na velmi jednoduchou korutinu, která před svým ukončením vyvolá výjimku:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    raise Exception(name)
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task, "1st", 10, 0.3)
        nursery.start_soon(task, "2nd", 10, 0.3)
        nursery.start_soon(task, "3rd", 10, 0.3)
    print("done")
 
 
trio.run(main)

Tato výjimka není nikde zachycena a proto „probublá“ až do kódu, který náš program spustil:

main started
1st task started
1st 1/10
2nd task started
2nd 1/10
3rd task started
3rd 1/10
1st 2/10
2nd 2/10
3rd 2/10
3rd 3/10
2nd 3/10
1st 3/10
3rd 4/10
2nd 4/10
1st 4/10
3rd 5/10
2nd 5/10
1st 5/10
3rd 6/10
2nd 6/10
1st 6/10
1st 7/10
2nd 7/10
3rd 7/10
1st 8/10
2nd 8/10
3rd 8/10
1st 9/10
2nd 9/10
3rd 9/10
1st 10/10
2nd 10/10
3rd 10/10
Traceback (most recent call last):
  File "trio_07.py", line 24, in <Module>
    trio.run(main)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 1946, in run
    raise runner.main_task_outcome.error
  File "trio_07.py", line 20, in main
    nursery.start_soon(task, "3rd", 10, 0.3)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 813, in __aexit__
    raise combined_error_from_nursery
  File "trio_07.py", line 11, in task
    raise Exception(name)
Exception: 3rd
Poznámka: povšimněte si, že z výjimky, resp. přesněji řečeno z výpisu obsahu zásobníkových rámců, je zcela zřejmé, na kterém místě výjimka vznikla a taktéž to, že „probublala“ (korektně) přes funkci main.

10. Zachycení výjimek vyhazovaných z korutin

Výjimku vyhozenou z korutiny lze zachytit v programovém kódu, který korutinu vytvořil a spustil (tedy buď přímo uvnitř nebo vně bloku async with). Tento koncept je ukázán na následujícím příkladu:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    raise Exception(name)
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    try:
        async with trio.open_nursery() as nursery:
            nursery.start_soon(task, "1st", 10, 0.3)
            nursery.start_soon(task, "2nd", 10, 0.3)
            nursery.start_soon(task, "3rd", 10, 0.3)
    except Exception as e:
        print("Caught", e)
    print("done")
 
 
trio.run(main)

Zde výjimka vede k ukončení bloku async with a tím pádem i k ukončení korutin:

main started
3rd task started
3rd 1/10
2nd task started
2nd 1/10
1st task started
1st 1/10
1st 2/10
2nd 2/10
3rd 2/10
3rd 3/10
2nd 3/10
1st 3/10
1st 4/10
2nd 4/10
3rd 4/10
1st 5/10
2nd 5/10
3rd 5/10
3rd 6/10
2nd 6/10
1st 6/10
3rd 7/10
2nd 7/10
1st 7/10
1st 8/10
2nd 8/10
3rd 8/10
1st 9/10
2nd 9/10
3rd 9/10
3rd 10/10
2nd 10/10
1st 10/10
Caught 3rd
done

Ještě lépe bude chování patrné v situaci, kdy jedna z korutin skončí (s výjimkou) dříve, než korutiny další:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    raise Exception(name)
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    try:
        async with trio.open_nursery() as nursery:
            nursery.start_soon(task, "1st", 10, 1.0)
            nursery.start_soon(task, "2nd", 10, 1.0)
            nursery.start_soon(task, "3rd", 10, 0.1)
    except Exception as e:
        print("Caught", e)
    print("done")
 
 
trio.run(main)

Z výpisu je patrné, že výjimka způsobí ukončení dalších korutin:

main started
3rd task started
3rd 1/10
2nd task started
2nd 1/10
1st task started
1st 1/10
3rd 2/10
3rd 3/10
3rd 4/10
3rd 5/10
3rd 6/10
3rd 7/10
3rd 8/10
3rd 9/10
3rd 10/10
2nd 2/10
1st 2/10
Caught 3rd
done
Poznámka: příště si ukážeme, jakým způsobem je možné toto chování změnit, protože ne vždy musí být vyhovující.

11. Vznik výjimek souběžně v několika korutinách

V praxi může nastat situace, kdy vznikne několik výjimek v souběžně běžících korutinách. Jak je však možné na tyto výjimky reagovat, když celý koncept výjimek počítá se vznikem jediné výjimky? Knihovna Trio poskytuje velmi elegantní řešení. Podívejme se nejprve na příklad v němž vzniknou tři výjimky, každá jiného typu:

import trio
 
 
async def task1():
    dct = {}
    return dct["foo"]
 
 
async def task2():
    l = []
    return l[10]
 
 
async def task3():
    x = 0
    return 1/x
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        nursery.start_soon(task1)
        nursery.start_soon(task2)
        nursery.start_soon(task3)
    print("done")
 
 
trio.run(main)

Vidíme, že každá korutina skutečně vyhodí výjimku, pokaždé jiného typu. Po spuštění příkladu by tedy mělo být možné nějakým způsobem všechny tyto výjimky zpracovat:

main started
Traceback (most recent call last):
  File "trio_10.py", line 28, in
    trio.run(main)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 1946, in run
    raise runner.main_task_outcome.error
  File "trio_10.py", line 24, in main
    nursery.start_soon(task3)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 813, in __aexit__
    raise combined_error_from_nursery
trio.MultiError: ZeroDivisionError('division by zero'), IndexError('list index out of range'), KeyError('foo')
 
Details of embedded exception 1:
 
  Traceback (most recent call last):
    File "trio_10.py", line 16, in task3
      return 1/x
  ZeroDivisionError: division by zero
 
Details of embedded exception 2:
 
  Traceback (most recent call last):
    File "trio_10.py", line 11, in task2
      return l[10]
  IndexError: list index out of range
 
Details of embedded exception 3:
 
  Traceback (most recent call last):
    File "trio_10.py", line 6, in task1
      return dct["foo"]
  KeyError: 'foo'

Vidíme, že byla vyhozena jediná výjimka typu MultiError, která však obsahuje přesné informace o všech výjimkách, které v korutinách skutečně vznikly.

12. Je počet korutin (souběžných úloh) prakticky omezen?

Počet současně spuštěných procesů bývá omezen praktickými možnostmi plánovače operačního systému. V podstatě totéž je možné říci o vláknech, jejichž využití je zejména v Pythonu navíc omezeno i existencí GILu. Jak je však tomu v případě souběžných úloh neboli korutin? Ty jsou – co se týče paralelního běhu – taktéž omezeny GILem, zajímavé ovšem bude taktéž ověřit, jaké jsou paměťové nároky korutin popř. do jaké míry existují omezení v oblasti jejich plánování.

V následujícím příkladu je spuštěno 100 korutin, které poběží souběžně po dobu 100 sekund (což je dostatečný čas na provedení měření):

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        for i in range(100):
            nursery.start_soon(task, f"Task {i}", 1, 100)
    print("done")
 
 
trio.run(main)

Po spuštění tohoto programu zjistíme číslo odpovídajícího procesu:

$ ps ax |grep python
 
    614 ?        Ss     0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
   4410 ?        S      0:59 /usr/bin/python3 /usr/share/system-config-printer/applet.py
   4428 ?        S      0:00 python3 /usr/lib/blueberry/safechild /usr/sbin/rfkill event
3049128 pts/2    Ss+    0:00 python3 trio_11.py
3049130 pts/3    S+     0:00 grep --color=auto python

A následně si necháme vypsat jeho paměťové nároky:

$ pmap 3049128
 
3049128:   python3 trio_11.py
00007f74803bc000      4K rw---   [ anon ]
00007ffec21cb000    132K rw---   [ stack ]
00007ffec21f4000     12K r----   [ anon ]
00007ffec21f7000      4K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total            31772K

V rámci další kapitoly pak počet procesů zvýšíme na 10000, což už je číslo, které je v případě použití procesů či vláken velmi problematické.

13. Paměťové nároky programu s 10000 korutinami

Podívejme se nyní na paměťové nároky programu, který namísto pouhých sto korutin vytvoří 10000 korutin, jenž budou opravdu v daný okamžik existovat „souběžně“. Každá korutina totiž bude volat korutinu trio.sleep, přičemž čas čekání bude nastaven na 10000 sekund, což je dostatečně dlouhá doba na prozkoumání paměťových nároků takového programu:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        for i in range(10000):
            nursery.start_soon(task, f"Task {i}", 1, 10000)
    print("done")
 
 
trio.run(main)

Po spuštění tohoto programu nejdříve zjistíme odpovídající číslo procesu (PID):

$ ps ax |grep python
    614 ?        Ss     0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
   4410 ?        S      0:59 /usr/bin/python3 /usr/share/system-config-printer/applet.py
   4428 ?        S      0:00 python3 /usr/lib/blueberry/safechild /usr/sbin/rfkill event
3049130 pts/2    Ss+    0:00 python3 trio_11.py
3049132 pts/3    S+     0:00 grep --color=auto python

A následně si necháme vypsat paměťové nároky tohoto procesu:

$ pmap 3049130
3049128:   python3 trio_11.py
 
00007fa95aed5000     28K r--s- gconv-modules.cache
00007fa95aedc000      4K r---- ld-2.31.so
00007fa95aedd000    140K r-x-- ld-2.31.so
00007fa95af00000     32K r---- ld-2.31.so
00007fa95af09000      4K r---- ld-2.31.so
00007fa95af0a000      4K rw--- ld-2.31.so
00007fa95af0b000      4K rw---   [ anon ]
00007fff488fe000    132K rw---   [ stack ]
00007fff489f8000     12K r----   [ anon ]
00007fff489fb000      4K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]
 total            87168K

Z výpisu je patrné, že proces s 10000 korutinami v paměti zabírá přibližně 87MB, což zhruba odpovídá devíti kilobajtům na korutinu (což je zcela nepřesný výpočet, protože obsazená paměť sice poroste lineárně, ovšem nebude začínat v nule).

Poznámka: tento demonstrační příklad je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio12.py.

14. Spuštění 10000 souběžných úloh se sledováním vytížení CPU

V následujícím demonstračním příkladu spustíme 10000 souběžných úloh, ovšem tak, že doba trvání operace trio.sleep() bude snížena na minimum – jedinou sekundu. Budeme přitom zkoumat vytížení CPU:

import trio
 
 
async def task(name, n, s):
    print(f"{name} task started")
 
    for i in range(n):
        print(f"{name} {i+1}/{n}")
        await trio.sleep(s)
 
    print(f"{name} task finished")
 
 
async def main():
    print("main started")
    async with trio.open_nursery() as nursery:
        for i in range(10000):
            nursery.start_soon(task, f"Task {i}", 10, 1)
    print("done")
 
 
trio.run(main)

Takto spuštěný proces bude (obecně) v daný okamžik běžet na jediném jádru, které bude vytíženo téměř na sto procent, což je ostatně patrné i při pohledu na následující screenshot získaný nástrojem top:

Obrázek 1: Vytížení CPU na systému s osmi procesorovými jádry.

Poznámka: tento demonstrační příklad je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio13.py.

15. Komunikace mezi souběžnými úlohami s využitím kanálů

Knihovny či v některých případech dokonce i nové jazykové konstrukce umožňující používání kanálů (či front) pro asynchronní komunikaci mezi různými částmi vyvíjených aplikací, se v posledních několika letech těší poměrně velké popularitě. Ta je způsobena především dvěma faktory. První důvod spočívá ve snaze o zjednodušení návrhu (či porozumění) vyvíjené aplikace, zejména ve chvíli, kdy se v rámci jednoho programu předávají data (resp. objekty) mezi částmi, jejichž funkce může být dobře izolována od částí ostatních.

Druhý důvod je poněkud prozaičtější – v některých situacích je nutné dosáhnout zvýšení efektivity celé aplikace (například zvýšit počet odpovědí, které může server vygenerovat za určitou časovou jednotku) a přitom není možné či vhodné využívat řešení založené na použití většího množství vláken spravovaných přímo operačním systémem. Naprosto typickým příkladem jsou virtuální stroje JavaScriptu, které povětšinou umožňují běh aplikace v jediném vláknu (což je ovšem s ohledem na „kvalitu“ některých programových kódů spíše výhodou…).

Některé programovací jazyky, zejména pak v tomto paralelně běžícím seriálu popisovaný jazyk Go, obsahují prostředky sloužící pro zajištění asynchronní komunikace přímo v syntaxi (a samozřejmě též v sémantice) jazyka. Konkrétně v případě jazyka Go se jedná o takzvané gorutiny, které jsou doplněny o specializované operace sloužící pro zápis či čtení dat z kanálů (channels). Tyto specializované operace jsou v jazyce Go představovány operátorem <- (ten má dva významy v závislosti na tom, zda je před operátorem uveden identifikátor představující kanál či nikoli).

Knihovna Trio je založena na korutinách, které sice běží souběžně, ale nikoli nutně paralelně. Nicméně i mezi korutinami je mnohdy nutné předávat data. I zde se pro tento účel používají kanály neboli channels. Ty se na jedné straně podobají frontám (queue), na straně druhé však mají poněkud odlišnou sémantiku s pevným rozdělením na část určenou pro posílání data a na část určenou pro čtení dat.

16. Základní vlastnosti kanálů nabízených knihovnou Trio

Kanálem je v knihovně Trio myšlena interní datová struktura sloužící pro zápis dat jednou úlohou (či větším množstvím úloh) a čtením dat další úlohou (nebo úlohami). Z pohledu programátora je kanál reprezentován dvojicí objektů, přičemž jeden z těchto objektů slouží pro zápis dat a druhý pro jejich čtení. To je největší rozdíl oproti klasickým frontám, kde jeden objekt (opět z pohledu programátora) nabízí obě operace – zápis i čtení dat. Kanál má specifikovanou kapacitu, tedy počet zpráv, které v něm mohou být uloženy, možné je vytvořit i kanál s nulovou kapacitou, což znamená, že každý zápis dat je blokující a musí být následován jejich čtením (z druhé strany kanálu). Kanál je možné uzavřít metodou aclose, ovšem toto uzavření je provedeno automaticky v případě, že je kanál vytvořen v bloku async with. Následuje typický příklad konstrukce kanálu se získáním dvojice objektů – jeden je určený pro zápis dat, druhý pro čtení dat. Kapacita je nulová:

async with trio.open_nursery() as nursery:
    send_channel, receive_channel = trio.open_memory_channel(0)

Konstrukce kanálu s omezenou kapacitou interního bufferu:

async with trio.open_nursery() as nursery:
    send_channel, receive_channel = trio.open_memory_channel(100)

Konstrukce kanálu s neomezenou kapacitou interního bufferu:

async with trio.open_nursery() as nursery:
    send_channel, receive_channel = trio.open_memory_channel(math.inf)

17. Ukázka klasické úlohy typu producent-konzument

Klasickou úlohu typu producent-konzument s jediným producentem a jediným konzumentem je možné v knihovně Trio zapsat zcela idiomatickým způsobem. Producent použije „vysílací“ část kanálu a producent část „přijímací“. Jak producent, tak i konzument jsou vytvořeny společně s kanálem v bloku async with, čímž je zajištěno automatické uzavření všech prostředků. Tento demonstrační příklad lze později rozšířit, například tak, aby se použil kanál s kapacitou, větší množství producentů a/nebo větší množství konzumentů:

ict ve školství 24

import trio
 
 
async def producer(send_channel):
    for i in range(1, 10):
        message = f"message {i}"
        print(f"Producer: {message}")
        await send_channel.send(message)
 
 
async def consumer(receive_channel):
    async for value in receive_channel:
        print(f"Consumer: received{value!r}")
        await trio.sleep(1)
 
 
async def main():
    async with trio.open_nursery() as nursery:
        send_channel, receive_channel = trio.open_memory_channel(0)
        nursery.start_soon(producer, send_channel)
        nursery.start_soon(consumer, receive_channel)
 
 
trio.run(main)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio14.py.

18. Obsah druhé části článku

Knihovna Trio nabízí programátorům i některé další užitečné techniky. Jedná se například o možnost předčasného ukončení souběžně běžících úloh, specifikace maximálního času pro provedení úlohy (timeout), vytvoření souběžných úloh z jiných souběžných úloh a v neposlední řadě o podporu monitoringu celé aplikace. S těmito technikami se podrobněji seznámíme v samostatném článku.

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Stručný popis příkladu Cesta
1 multithreading1.py spuštění tří vláken vykonávajících déletrvající činnost https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading1.py
2 multithreading2.py spuštění tří vláken, předání parametrů volaným funkcím https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading2.py
3 multithreading3.py explicitní čekání na dokončení běhu vláken metodou join https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading3.py
4 multithreading4.py sdílený objekt https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading4.py
5 multithreading_join_deamon.py čekání na dokončení vláken s příznakem „daemon“ https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading_join_dea­mon.py
6 multithreading_no_join_deamon.py vlákna s příznakem „daemon“, na jejichž ukončení se nečeká https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading_no_join_de­amon.py
7 multithreading_no_join_no_deamon.py běžná vlákna bez příznaku „daemon“ https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading_no_join_no_de­amon.py
8 multithreading_timeout.py specifikace maximální doby čekání na ukončení vlákna https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multithreading_timeout.py
       
9 multiprocessing1.py zavolání funkce spuštěné v rámci dalšího procesu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing1.py
10 multiprocessing2.py spuštění většího množství procesů https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing2.py
11 multiprocessing3.py nepatrná úprava předchozího příkladu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing3.py
12 multiprocessing4.py řízení workerů posílanými příkazy https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing4.py
13 multiprocessing5.py řízení workerů posílanými příkazy https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing5.py
14 multiprocessing6.py jeden proces a sdílená globální hodnota https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing6.py
15 multiprocessing7.py více procesů, které nesdílí hodnoty https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/multiprocessing7.py
       
16 queue_example.py základní vlastnosti sdílené datové struktury Queue https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/queue_example.py
17 simple_queue_example.py základní vlastnosti sdílené datové struktury SimpleQueue https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/simple_queue_example.py
18 priority_queue_example.py základní vlastnosti sdílené datové struktury PriorityQueue https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/priority_queue_example.py
       
19 queues1.py komunikace mezi vlákny s využitím front: základní forma https://github.com/tisnik/most-popular-python-libs/blob/master/concurrent/queues1.py
20 queues2.py komunikace mezi vlákny s využitím front: více konzumentů https://github.com/tisnik/most-popular-python-libs/blob/master/concurrent/queues2.py
21 queues3.py komunikace mezi vlákny s využitím front: více producentů https://github.com/tisnik/most-popular-python-libs/blob/master/concurrent/queues3.py
22 queues4.py komunikace mezi vlákny s využitím front: více producentů i konzumentů https://github.com/tisnik/most-popular-python-libs/blob/master/concurrent/queues4.py
       
23 thread_pool1.py spuštění tří úloh ve třech vláknech https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool1.py
24 thread_pool2.py spuštění deseti úloh v deseti vláknech https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool2.py
25 thread_pool3.py omezení počtu vláken na 3 pro celkem deset úloh https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool3.py
26 thread_pool4.py návratová hodnota získaná po spuštění úlohy https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool4.py
27 thread_pool5.py získání vypočtených hodnot https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool5.py
28 thread_pool6.py alternativní způsob zápisu předchozího příkladu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/thread_pool6.py
       
29 process_pool1.py spuštění tří úloh ve vlastních procesech https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/process_pool1.py
30 process_pool2.py návratové hodnoty https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/process_pool2.py
31 process_pool3.py čekání na dokončení úloh + získání návratových hodnot https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/process_pool3.py
       
32 async_await1.py základní způsob použití async a await https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_await1.py
33 async_await2.py funkce main volaná asynchronně https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_await2.py
34 async_await3.py dvě asynchronně běžící úlohy https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_await3.py
35 async_await4.py získání výsledků z asynchronně běžících úloh https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_await4.py
36 async_queue1.py fronty pro kooperace mezi korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_queue1.py
37 async_queue2.py korektní spuštění většího množství korutin https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_queue2.py
38 async_queue3.py využití asyncio.gather https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_queue3.py
39 async_aiohttp1.py použití knihovny aiohttp https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_aiohttp1.py
40 async_aiohttp2.py záznam časů trvání jednotlivých operací https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_aiohttp2.py
41 async_aiohttp3.py vylepšení předchozího příkladu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_aiohttp3.py
42 async_aiohttp4.py využití deseti korutin https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_aiohttp4.py
       
43 curio01.py základní konstrukce nabízené knihovnou curio (curio.run) https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio01.py
44 curio02.py předání parametrů asynchronně volané korutině při volání curio.run https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio02.py
45 curio03.py chování programu při spuštění několika korutin funkcí curio.run https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio03.py
46 curio04.py asynchronní spuštění korutin pomocí curio.spawn (nekorektní příklad) https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio04.py
47 curio05.py asynchronní spuštění korutin pomocí curio.spawn (korektní příklad) https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio05.py
48 curio06.py čekání na dokončení korutin s využitím metody task.join https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio06.py
49 curio07.py spuštění monitoru https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio07.py
50 curio08.py využití fronty pro předávání parametrů https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio08.py
51 curio09.py datová struktura curio.UniversalQueue https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio09.py
52 curio10.py klasický program typu producent-konzument https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio10.py
53 curio11.py výsledky vrácené korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio11.py
54 curio12.py výsledky vrácené dlouho běžícími korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio12.py
55 curio13.py čekání na výsledky po stanovený mezní časový interval https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio13.py
56 curio14.py reakce na vypršení mezního časového intervalu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio14.py
57 curio15.py výjimka vzniklá v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio14.py
58 curio16.py reakce na výjimku vyhozenou v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/curio16.py
       
59 with_block.py blok with a context manager https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/with_block.py
60 async_with_block.py blok async with a asynchronní context manager https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/async_with_block.py
       
61 trio01.py spuštění korutiny knihovnou Trio https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio01.py
62 trio01_error.py chybné vynechání slova awai při volání jiné korutiny https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio01_error.py
63 trio02.py déletrvající souběžně běžící úloha https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio02.py
64 trio03.py tři souběžně běžící úlohy https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio03.py
65 trio04.py základní způsob použití objektu nursery https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio04.py
66 trio05.py hodnota získaná po spuštění korutiny https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio05.py
67 trio06.py trojice postupně spuštěných korutin https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio06.py
68 trio07.py vyhození výjimky v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio07.py
69 trio08.py vyhození výjimky v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio08.py
70 trio09.py pokus o zachycení výjimky v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio09.py
71 trio10.py vznik výjimek v několika korutinách https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio10.py
72 trio11.py paměťové nároky programu se 100 korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio11.py
73 trio12.py paměťové nároky programu s 10000 korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio12.py
74 trio13.py spuštění 10000 souběžných úloh https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio13.py
75 trio14.py ukázka klasické úlohy typu producent-konzument https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio14.py

20. Odkazy na Internetu

  1. Dokumentace Pythonu: balíček queue
    https://docs.python.org/3/li­brary/queue.html
  2. Dokumentace Pythonu: balíček threading
    https://docs.python.org/3/li­brary/threading.html?
  3. Dokumentace Pythonu: balíček multiprocessing
    https://docs.python.org/3/li­brary/multiprocessing.html
  4. Dokumentace Pythonu: balíček asyncio
    https://docs.python.org/3/li­brary/asyncio.html
  5. Synchronization Primitives
    https://docs.python.org/3/li­brary/asyncio-sync.html
  6. Coroutines
    https://docs.python.org/3/li­brary/asyncio-task.html
  7. Queues
    https://docs.python.org/3/li­brary/asyncio-queue.html
  8. python-csp
    https://python-csp.readthedocs.io/en/latest/
  9. TrellisSTM
    http://peak.telecommunity­.com/DevCenter/TrellisSTM
  10. Python Multithreading and Multiprocessing Tutorial
    https://www.toptal.com/pyt­hon/beginners-guide-to-concurrency-and-parallelism-in-python
  11. ThreadPoolExecutor
    https://docs.python.org/3/li­brary/concurrent.futures.html#thre­adpoolexecutor
  12. ProcessPoolExecutor
    https://docs.python.org/3/li­brary/concurrent.futures.html#pro­cesspoolexecutor
  13. asyncio — Asynchronous I/O
    https://docs.python.org/3/li­brary/asyncio.html
  14. Threads vs Async: Has Asyncio Solved Concurrency?
    https://www.youtube.com/wat­ch?v=NZq31Sg8R9E
  15. Python Asynchronous Programming – AsyncIO & Async/Await
    https://www.youtube.com/wat­ch?v=t5Bo1Je9EmE
  16. AsyncIO & Asynchronous Programming in Python
    https://www.youtube.com/wat­ch?v=6RbJYN7SoRs
  17. Coroutines and Tasks
    https://docs.python.org/3/li­brary/asyncio-task.html
  18. Python async/await Tutorial
    https://stackabuse.com/python-async-await-tutorial/
  19. Demystifying Python's Async and Await Keywords
    https://www.youtube.com/wat­ch?v=F19R_M4Nay4
  20. Curio
    https://curio.readthedocs­.io/en/latest/
  21. Trio: a friendly Python library for async concurrency and I/O
    https://trio.readthedocs.i­o/en/stable/
  22. Curio – A Tutorial Introduction
    https://curio.readthedocs­.io/en/latest/tutorial.html
  23. unsync
    https://github.com/alex-sherman/unsync
  24. David Beazley – Die Threads
    https://www.youtube.com/wat­ch?v=xOyJiN3yGfU
  25. Miguel Grinberg Asynchronous Python for the Complete Beginner PyCon 2017
    https://www.youtube.com/wat­ch?v=iG6fr81×HKA
  26. Build Your Own Async
    https://www.youtube.com/wat­ch?v=Y4Gt3Xjd7G8
  27. The Other Async (Threads + Async = ❤️)
    https://www.youtube.com/wat­ch?v=x1ndXuw7S0s
  28. Fear and Awaiting in Async: A Savage Journey to the Heart of the Coroutine Dream
    https://www.youtube.com/watch?v=E-1Y4kSsAFc
  29. Keynote David Beazley – Topics of Interest (Python Asyncio)
    https://www.youtube.com/wat­ch?v=ZzfHjytDceU
  30. David Beazley – Python Concurrency From the Ground Up: LIVE! – PyCon 2015
    https://www.youtube.com/wat­ch?v=MCs5OvhV9S4
  31. Python Async basics video (100 million HTTP requests)
    https://www.youtube.com/watch?v=Mj-Pyg4gsPs
  32. Nathaniel J. Smith – Trio: Async concurrency for mere mortals – PyCon 2018
    https://www.youtube.com/wat­ch?v=oLkfnc_UMcE
  33. Timeouts and cancellation for humans
    https://vorpus.org/blog/timeouts-and-cancellation-for-humans/
  34. What is the core difference between asyncio and trio?
    https://stackoverflow.com/qu­estions/49482969/what-is-the-core-difference-between-asyncio-and-trio
  35. Some thoughts on asynchronous API design in a post-async/await world
    https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#the-curious-effectiveness-of-curio
  36. Companion post for my PyCon 2018 talk on async concurrency using Trio
    https://vorpus.org/blog/companion-post-for-my-pycon-2018-talk-on-async-concurrency-using-trio/
  37. Control-C handling in Python and Trio
    https://vorpus.org/blog/control-c-handling-in-python-and-trio/
  38. Context Managers and Python's with Statement
    https://realpython.com/python-with-statement/
  39. Notes on structured concurrency, or: Go statement considered harmful
    https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
  40. Structured concurrency explained – Part 1: Introduction
    https://www.thedevtavern.com/blog/pos­ts/structured-concurrency-explained/
  41. Structured concurrency
    https://en.wikipedia.org/wi­ki/Structured_concurrency
  42. Structured Concurrency
    https://250bpm.com/blog:71/
  43. Python and Trio, where producers are consumers, how to exit gracefully when the job is done?
    https://stackoverflow.com/qu­estions/65304775/python-and-trio-where-producers-are-consumers-how-to-exit-gracefully-when-the

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.