Hlavní navigace

Souběžné a paralelně běžící úlohy naprogramované v Pythonu – závěrečné zhodnocení

19. 5. 2022
Doba čtení: 34 minut

Sdílet

 Autor: Depositphotos
V šestém článku o vytváření, spouštění a řízení souběžných popř. paralelně běžících úloh v Pythonu nejdříve dokončíme popis knihovny Trio a posléze provedeme zhodnocení jednotlivých technologií.

Obsah

1. Základní řešení problému typu producent-konzument v knihovně Trio

2. Specifikace maximální doby čekání na zprávu popř. pro poslání zprávy do blokovaného kanálu

3. Přeskočení blokující operace namísto vyhození výjimky

4. Explicitní uzavření kanálu v korutině, reakce na uzavření kanálu dalšími korutinami

5. Problematika uzavření kanálu používaného větším množstvím korutin

6. Vyřešení předchozího problému – „naklonování“ kanálu

7. Využití kontextových informací

8. Zámky

9. Příklad použití zámku

10. Férové získávání zámků

11. Shrnutí – technologie pro souběžné a paralelní úlohy v Pythonu

12. Tři nejčastěji používané strategie

13. Jaké řešení je tedy nejvhodnější?

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

15. Předchozí články z miniseriálu o souběžných úlohách v Pythonu

16. Odkazy na Internetu

1. Základní řešení problému typu producent-konzument v knihovně Trio

V první polovině dnešního článku dokončíme popis problematiky, které jsme se věnovali již minule. Připomeňme si, že jsme se zabývali způsobem komunikace mezi korutinami spravovanými knihovnou Trio s využitím jednosměrných komunikačních kanálů (channel), které mohou být použity buď jako jednoduché mailboxy, nebo mohou mít přiřazen buffer s určitou kapacitou (potom se do jisté míry chovají jako fronty – queue).

Mnoho reálných úloh je obdobou problematiky producent-konzument, což je architektura, v níž jsou od sebe odděleny zdroje dat (nebo úloh) a cíle dat (vykonavatelé úloh). Jak producenti, tak i konzumenti mohou být realizovány s využitím korutin, což je zvláště užitečné ve chvíli, kdy vykonávají mnoho I/O operací. V případě použití knihovny Trio může komunikace probíhat s využitím kanálů resp. v tom nejjednodušším případě s využitím jediného kanálu:

import trio
 
 
num_producers = 5
num_consumers = 20
 
 
async def producer(id, send_channel):
    for i in range(1, 10):
        message = f"message {i}"
        print(f"Producer #{id}: {message}")
        await send_channel.send(message)
 
 
async def consumer(id, receive_channel):
    async for value in receive_channel:
        print(f"Consumer #{id}: 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)
        for id in range(num_producers):
            nursery.start_soon(producer, id, send_channel)
        for id in range(num_consumers):
            nursery.start_soon(consumer, id, 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/trio25_multiple_prod_con­s.py.

Obecně platí, že se producenti budou o zaslané úlohy dělit:

Producer #4: message 1
Producer #3: message 1
Producer #2: message 1
Producer #1: message 1
Producer #0: message 1
Producer #0: message 2
Consumer #15: received'message 1'
Producer #1: message 2
Consumer #16: received'message 1'
Producer #2: message 2
Consumer #17: received'message 1'
Producer #3: message 2
Consumer #18: received'message 1'
Producer #4: message 2
Consumer #19: received'message 1'
...
...
...
Consumer #16: received'message 8'
Producer #2: message 9
Consumer #17: received'message 8'
Producer #3: message 9
Consumer #18: received'message 8'
Producer #4: message 9
Consumer #19: received'message 8'
Consumer #6: received'message 9'
Consumer #5: received'message 9'
Consumer #0: received'message 9'
Consumer #1: received'message 9'
Consumer #2: received'message 9'

V dalších kapitolách si popíšeme některé problémy, které mohou nastat i způsoby jejich řešení.

2. Specifikace maximální doby čekání na zprávu, popř. pro poslání zprávy do blokovaného kanálu

Poslání zprávy do kanálu či naopak přečtení zprávy z kanálu je obecně blokující operace, která ve výchozím nastavení bude čekat potenciálně nekonečnou dobu na to, až bude kanál uvolněn pro poslání zprávy či naopak až se v něm objeví zpráva, na kterou čeká konzument. Ovšem, jak jsme si již řekli minule, je možné specifikovat maximální dobu čekání na dokončení nějaké operace či operací. V našem konkrétním případě to znamená, že se bude jednat o operaci poslání zprávy:

with trio.fail_after(producer_timeout):
    await send_channel.send(message)

Nebo naopak o operaci příjmu zprávy:

with trio.fail_after(consumer_timeout):
    value = await receive_channel.receive()
    print(f"Consumer #{id}: received{value!r}")
    await trio.sleep(1)

V obou uvedených případech platí, že pokud nedojde k dokončení operace v nastaveném čase je vyhozena výjimka, na kterou lze v dalším kódu adekvátně reagovat.

Podívejme se nyní na demonstrační příklad, kde je tento koncept použit:

import trio
 
 
num_producers = 5
num_consumers = 20
consumer_timeout = 2
producer_timeout = 2
 
 
async def producer(id, send_channel):
    for i in range(1, 10):
        message = f"message {i}"
        print(f"Producer #{id}: {message}")
        with trio.fail_after(producer_timeout):
            await send_channel.send(message)
 
 
async def consumer(id, receive_channel):
    while True:
        with trio.fail_after(consumer_timeout):
            value = await receive_channel.receive()
            print(f"Consumer #{id}: 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)
        for id in range(num_producers):
            nursery.start_soon(producer, id, send_channel)
        for id in range(num_consumers):
            nursery.start_soon(consumer, id, 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/trio26_timeouts.py.

Po spuštění skriptu začnou pracovat jak producenti, tak i konzumenti:

Producer #4: message 1
Producer #3: message 1
Producer #2: message 1
Producer #1: message 1
Producer #0: message 1
Consumer #19: received'message 1'
Producer #4: message 2
Consumer #18: received'message 1'
Producer #3: message 2
Consumer #17: received'message 1'
Producer #2: message 2
Consumer #16: received'message 1'

Producer #4: message 1
Producer #3: message 1
Producer #2: message 1
Producer #1: message 1
Producer #0: message 1
Consumer #19: received'message 1'
Producer #4: message 2
Consumer #18: received'message 1'
Producer #3: message 2
Consumer #17: received'message 1'
Producer #2: message 2
Consumer #16: received'message 1'
...
...
...

Ovšem každý producent vytvoří pouze určitý počet zpráv a potom je ukončen. Následně je vyhozena výjimka typu TooSlowError následovaná ukončením všech operací:

Producer #3: message 5
Consumer #0: received'message 4'
Producer #4: message 5
Traceback (most recent call last):
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_timeouts.py", line 106, in fail_at
    yield scope
  File "trio_26_timeouts.py", line 23, in consumer
    await trio.sleep(1)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_timeouts.py", line 76, in sleep
    await sleep_until(trio.current_time() + seconds)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_timeouts.py", line 57, in sleep_until
    await sleep_forever()
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_timeouts.py", line 40, in sleep_forever
    await trio.lowlevel.wait_task_rescheduled(lambda _: trio.lowlevel.Abort.SUCCEEDED)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
    return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/outcome/_impl.py", line 138, in unwrap
    raise captured_error
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 1173, in raise_cancel
    raise Cancelled._create()
trio.Cancelled: Cancelled
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "trio_26_timeouts.py", line 35, 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_26_timeouts.py", line 32, in main
    nursery.start_soon(consumer, id, receive_channel)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 813, in __aexit__
    raise combined_error_from_nursery
  File "trio_26_timeouts.py", line 23, in consumer
    await trio.sleep(1)
  File "/usr/lib/python3.8/contextlib.py", line 131, in __exit__
    self.gen.throw(type, value, traceback)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_timeouts.py", line 108, in fail_at
    raise TooSlowError
trio.TooSlowError

3. Přeskočení blokující operace namísto vyhození výjimky

V případě timeoutu se obecně očekávají dvě reakce – buď dojde k vyhození výjimky, nebo se bude operace posílání/příjmu do kanálu ignorovat. Prozatím umíme vyhodit výjimku, pokud je kanál blokovaný po delší dobu, než je doba specifikovaná ve funkci fail_after. Alternativou může být pouhé přeskočení dané operace (tedy „pokus se deset sekund čekat na zprávu a potom pokračuj dále, pokud není zpráva přijata“). Tento alternativní přístup je možné realizovat s využitím funkce move_on_after. Podívejme se tedy na způsob úpravy předchozího demonstračního příkladu tak, aby byl použit tento alternativní přístup:

import trio
 
 
num_producers = 5
num_consumers = 20
consumer_timeout = 2
producer_timeout = 2
 
 
async def producer(id, send_channel):
    for i in range(1, 10):
        message = f"message {i}"
        print(f"Producer #{id}: {message}")
        with trio.move_on_after(producer_timeout):
            await send_channel.send(message)
 
 
async def consumer(id, receive_channel):
    while True:
        print(f"Consumer #{id}: trying to receive message")
        with trio.move_on_after(consumer_timeout):
            value = await receive_channel.receive()
            print(f"Consumer #{id}: 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)
        for id in range(num_producers):
            nursery.start_soon(producer, id, send_channel)
        for id in range(num_consumers):
            nursery.start_soon(consumer, id, 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/trio_26B_move_after.py.

Chování po spuštění:

Consumer #19: trying to receive message
Consumer #18: trying to receive message
Consumer #17: trying to receive message
Consumer #16: trying to receive message
Consumer #15: trying to receive message
...
...
...

Začátek práce producentů:

...
...
...
Producer #4: message 1
Producer #3: message 1
Producer #2: message 1
Producer #1: message 1
Producer #0: message 1
Consumer #19: received'message 1'
Producer #4: message 2
Consumer #18: received'message 1'
Producer #3: message 2
Consumer #17: received'message 1'
Producer #2: message 2
Consumer #16: received'message 1'
...
...
...

Po poslání všech zpráv:

...
...
...
Consumer #14: received'message 9'
Consumer #5: received'message 9'
Consumer #6: received'message 9'
Consumer #7: received'message 9'
Consumer #13: trying to receive message
Consumer #14: trying to receive message
Consumer #5: trying to receive message
Consumer #6: trying to receive message
Consumer #7: trying to receive message
Consumer #12: trying to receive message
Consumer #11: trying to receive message
Consumer #10: trying to receive message
Consumer #15: trying to receive message
Consumer #16: trying to receive message
Consumer #17: trying to receive message
Consumer #18: trying to receive message
Consumer #19: trying to receive message
...
...
...

4. Explicitní uzavření kanálu v korutině, reakce na uzavření kanálu dalšími korutinami

Prozatím jsme na straně producenta používali přibližně tento programový kód (někdy doplněný o řešení timeoutů):

async def producer(id, send_channel):
    for i in range(1, 10):
        message = f"message {i}"
        print(f"Producer #{id}: {message}")
        await send_channel.send(message)

V takových případech producent pouze ukončil posílání zpráv, ovšem nijak neinformoval další korutiny, že již další zprávy nebude posílat. Samozřejmě by bylo možné navrhnout si nějaký komunikační protokol, ovšem existuje i jednodušší řešení – prostě komunikační kanál uzavřít. Stav kanálu (uzavřen/otevřen) je další informací, kterou mezi sebou korutiny mohou komunikovat, protože stav kanálu lze snadno zjistit. Automatické uzavření kanálu lze realizovat takto:

async with send_channel:
    ...
    ...
    ...
# po opuštění bloku with je kanál uzavřen

Na což může reagovat konzument ukončením smyčky async for:

async for value in receive_channel:
    ...
    ...
    ...
# pokud se dostaneme sem, byl kanál uzavřen

Podívejme se nyní na ucelený demonstrační příklad používající tento potenciálně velmi užitečný koncept:

import trio
 
 
async def producer(send_channel):
    async with 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)
    print("No more messages can be received!")
 
 
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/trio27_channel_close.py.

Nyní se chování příkladu poměrně zásadním způsobem změní, protože konzument dokáže správně zareagovat na situaci, kdy je kanál uzavřen – namísto čekání na další zprávu je přijímací programová smyčka jednoduše a bez dalšího čekání ukončena:

Producer: message 1
Producer: message 2
Consumer: received'message 1'
Consumer: received'message 2'
Producer: message 3
Producer: message 4
Consumer: received'message 3'
Consumer: received'message 4'
Producer: message 5
Consumer: received'message 5'
Producer: message 6
Producer: message 7
Consumer: received'message 6'
Consumer: received'message 7'
Producer: message 8
Producer: message 9
Consumer: received'message 8'
Consumer: received'message 9'
No more messages can be received!

5. Problematika uzavření kanálu používaného větším množstvím korutin

Předchozí příklad sice pracoval zcela korektně, ale bylo tomu pouze z toho důvodu, že byl použit pouze jediný producent zpráv. To vlastně znamená, že kanál byl uzavřen pouze jedenkrát. Ovšem ve chvíli, kdy by bylo použito větší množství producentů sdílejících stejný kanál, povede prakticky totožný programový kód k tomu, že se jednotliví producenti budou snažit kanál uzavřít popř. k tomu, že nějaký producent zapíše zprávu do již uzavřeného kanálu. Ostatně můžeme si to sami otestovat spuštěním následujícího skriptu:

import trio
 
 
num_producers = 5
num_consumers = 20
 
 
async def producer(id, send_channel):
    async with send_channel:
        for i in range(1, 10):
            message = f"message {i}"
            print(f"Producer #{id}: {message}")
            await send_channel.send(message)
 
 
async def consumer(id, receive_channel):
    async for value in receive_channel:
        print(f"Consumer #{id}: received{value!r}")
        await trio.sleep(id)
    print("No more messages can be received!")
 
 
async def main():
    async with trio.open_nursery() as nursery:
        send_channel, receive_channel = trio.open_memory_channel(0)
        for id in range(num_producers):
            nursery.start_soon(producer, id, send_channel)
        for id in range(num_consumers):
            nursery.start_soon(consumer, id, 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/trio28_multiple_channel_clo­se.py.

Skript (zcela podle očekávání) zpočátku pracuje korektně, ovšem ihned poté, co první (libovolný) producent kanál uzavře, dojde k vyhození výjimky při přístupu k již uzavřenému kanálu:

Producer #0: message 1
Producer #1: message 1
Producer #2: message 1
Producer #3: message 1
Producer #4: message 1
Consumer #4: received'message 1'
Producer #4: message 2
Consumer #3: received'message 1'
Producer #3: message 2
Consumer #2: received'message 1'
Producer #2: message 2
Consumer #1: received'message 1'
Producer #1: message 2
Consumer #0: received'message 1'
Producer #0: message 2
Consumer #5: received'message 2'
Producer #4: message 3
Consumer #6: received'message 2'
Producer #3: message 3
Consumer #7: received'message 2'
Producer #2: message 3
Consumer #8: received'message 2'
Producer #1: message 3
Consumer #9: received'message 2'
Producer #0: message 3
Consumer #10: received'message 3'
Producer #4: message 4
Consumer #11: received'message 3'
Producer #3: message 4
Consumer #12: received'message 3'
Producer #2: message 4
Consumer #13: received'message 3'
Producer #1: message 4
Consumer #14: received'message 3'
Producer #0: message 4
Consumer #15: received'message 4'
Consumer #0: received'message 8'
Producer #0: message 9
Consumer #0: received'message 9'
 
No more messages can be received!
Traceback (most recent call last):
  File "trio_28_multiple_channel_close.py", line 32, 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_28_multiple_channel_close.py", line 29, in main
    nursery.start_soon(consumer, id, receive_channel)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_run.py", line 813, in __aexit__
    raise combined_error_from_nursery
  File "trio_28_multiple_channel_close.py", line 13, in producer
    await send_channel.send(message)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_channel.py", line 178, in send
    await trio.lowlevel.wait_task_rescheduled(abort_fn)
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/trio/_core/_traps.py", line 166, in wait_task_rescheduled
    return (await _async_yield(WaitTaskRescheduled(abort_func))).unwrap()
  File "/home/ptisnovs/.local/lib/python3.8/site-packages/outcome/_impl.py", line 138, in unwrap
    raise captured_error
trio.ClosedResourceError

6. Vyřešení předchozího problému – „naklonování“ kanálu

Uzavírání kanálů producentem či konzumentem, aby se touto operací oznámilo ostatním komunikujícím stranám, že daná korutina už nebude posílat či přijímat zprávy, je poměrně elegantní a navíc i idiomatické řešení celého problému. Ovšem na druhou stranu pracuje korektně jen ve chvíli, kdy kanál uzavírá jediný producent či konzument, což by mohlo vést ke zbytečné komplikaci kódu (logika pro určení, kdo může a kdo již ne kanál zavřít). Knihovna Trio však programátorům nabízí řešení tohoto problému, a to „naklonováním“ kanálu. Z jednoho kanálu tak lze vytvořit klony, které ovšem sdílí data a ve chvíli, kdy je uzavřen poslední naklonovaný kanál, se uzavře i skutečný kanál. Toto řešení tedy přenáší celý problém na stranu Tria a bude vypadat následovně:

async with send_channel, receive_channel:
    for id in range(num_producers):
        nursery.start_soon(producer, id, send_channel.clone())
    for id in range(num_consumers):
        nursery.start_soon(consumer, id, receive_channel.clone())

Žádné další úpravy není zapotřebí provádět:

import trio
 
 
num_producers = 5
num_consumers = 20
 
 
async def producer(id, send_channel):
    async with send_channel:
        for i in range(1, 10):
            message = f"message {i}"
            print(f"Producer #{id}: {message}")
            await send_channel.send(message)
 
 
async def consumer(id, receive_channel):
    async for value in receive_channel:
        print(f"Consumer #{id}: received{value!r}")
        await trio.sleep(id)
    print("No more messages can be received!")
 
 
async def main():
    async with trio.open_nursery() as nursery:
        send_channel, receive_channel = trio.open_memory_channel(0)
        async with send_channel, receive_channel:
            for id in range(num_producers):
                nursery.start_soon(producer, id, send_channel.clone())
            for id in range(num_consumers):
                nursery.start_soon(consumer, id, receive_channel.clone())
 
 
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/trio29_multiple_channel_clo­se.py.

Podívejme se na chování kódu po jeho spuštění. Začátek práce korutin:

Producer #4: message 1
Producer #3: message 1
Producer #2: message 1
Producer #1: message 1
Producer #0: message 1
Consumer #19: received'message 1'
Producer #4: message 2
Consumer #18: received'message 1'
Producer #3: message 2
Consumer #17: received'message 1'
Producer #2: message 2
Consumer #16: received'message 1'
Producer #1: message 2
Consumer #15: received'message 1'

Ukončení práce producentů zpráv:

Producer #4: message 9
Consumer #0: received'message 8'
Consumer #0: received'message 9'
Consumer #0: received'message 9'
Consumer #0: received'message 9'
Consumer #0: received'message 9'
Consumer #0: received'message 9'
No more messages can be received!
No more messages can be received!
No more messages can be received!
No more messages can be received!
No more messages can be received!
No more messages can be received!

Nyní začínají konzumenti hlásit, že nedostanou další data a sami sebe korektně ukončí:

No more messages can be received!
No more messages can be received!
No more messages can be received!
No more messages can be received!
No more messages can be received!

7. Využití kontextových informací

V Pythonu nalezneme knihovnu nazvanou contextvars. Tato knihovna slouží pro vytváření a přístup ke kontextovým proměnným (či informacím), a to z mnoha míst kódu. Jedná se o vylepšenou alternativu ke globálním proměnným, s nimiž se v programech s větším množstvím korutin pracuje dost nešikovně (a obecně se jedná spíše o problematickou vlastnost jazyka). Naproti tomu je mnohdy užitečné mít možnost sdílet informace mezi korutinami bez toho, aby se do všech funkcí předávaly ty stejné parametry (například informace pro logování atd.). A právě v těchto případech je možné použít kontextové proměnné.

Balíček s podporou kontextových proměnných se importuje takto:

import contextvars

Kontextovou proměnnou lze kdykoli vytvořit zápisem:

additional_info = contextvars.ContextVar("additional_info")

Tato proměnná podporuje metody .get a .set, které lze volat z libovolné korutiny, bez nutnosti další synchronizace.

8. Zámky

V knihovně Trio nalezneme i realizaci klasických zámků (lock) neboli mutexů. Zámkem je v kontextu souběžných úloh myšleno synchronizační primitivum, které (pochopitelně při správném použití) zajišťuje výhradní přístup k nějakým sdíleným prostředkům, například k bloku paměti (sdílená proměnná či objekt), otevřenému socketu atd. Zámky obecně podporují dvě operace – acquire pro získání zámku a release pro jeho uvolnění. Přitom platí, že zámek může vlastnit pouze jediná korutina. V případě, že se dvě či více korutin pokusí o získání zámku, „vyhraje“ jedna z těchto korutin a další korutiny čekají na jeho uvolnění. Díky použití zámků lze tedy „serializovat“ přístup k nějakému prostředku. Hrozí však nebezpečí, že pokud budou korutiny pracovat s větším množstvím zámků, dojde k deadlocku.

Dostupné metody třídy trio.Lock:

  1. acquire
  2. acquire_nowait
  3. locked
  4. release
  5. statistics
Poznámka: používají se zejména již obě výše zmíněné operace acquire a release, a to nepřímo:
async with objekt_typu_zámek:
    ...
    ...
    ...

Do tohoto bloku se vstoupí jen ve chvíli, kdy korutina získá zámek. A po opuštění bloku dojde automaticky k uvolnění zámku, a to i v případě vyhození výjimky atd.

9. Příklad použití zámku

Podívejme se nyní na to, jakým způsobem lze se zámky pracovat v případě, že je vytvořeno několik korutin spouštějících totožný programový kód. Povšimněte si, že zámek je získán a současně i posléze uvolněn v bloku async with. To mj. znamená, že se na uvolnění zámku nezapomene. Navíc se jedná o idiomatický přístup, který známe i z dalších oblastí Tria:

import trio
 
 
num_workers = 10
 
 
async def worker(id, lock):
    while True:
        # pokus o ziskani zamku s jeho naslednym automatickym uvolnenim
        async with lock:
            print(f"Worker #{id}: acquires lock")
            await trio.sleep(1)
        # zde je jiz zamek uvolnen
 
 
async def main():
    async with trio.open_nursery() as nursery:
        # konstrukce zamku
        lock = trio.Lock()
        for id in range(num_workers):
            nursery.start_soon(worker, id, lock)
 
 
trio.run(main)

10. Férové získávání zámků

Zajímavé bude vysledovat, jakým způsobem je zámek po uvolnění předáván další korutině. Obecně totiž není specifikováno, které korutině bude zámek předán (a v mnoha programovacích jazycích resp. knihovnách tak může zámek stále získávat stále ta samá korutina a stále se bude jednat o korektní chování), ovšem v knihovně Trio (minimálně v současné verzi) je realizován algoritmus zajišťující určitou míru férovosti – viz též Should our locks (and semaphores, queues, etc.) be fair?. Ostatně spuštěním výše uvedeného demonstračního příkladu se můžeme sami přesvědčit, do jaké míry je předávání zámku férové či nikoli:

Worker #0: acquires lock
Worker #1: acquires lock
Worker #2: acquires lock
Worker #3: acquires lock
Worker #4: acquires lock
Worker #5: acquires lock
Worker #6: acquires lock
Worker #7: acquires lock
Worker #8: acquires lock
Worker #9: acquires lock
Worker #0: acquires lock
Worker #1: acquires lock
Worker #2: acquires lock
Worker #3: acquires lock
Worker #4: acquires lock
Worker #5: acquires lock
...
...
...
Poznámka: z předchozího úryvku výsledků je patrné, že se skutečně jedná o férový algoritmus typu round-robin.

11. Shrnutí – technologie pro souběžné a paralelní úlohy v Pythonu

V dnes končícím miniseriálu o souběžných, popř. v některých případech i paralelních úlohách realizovaných v programovacím jazyku Python jsme se seznámili s několika zajímavými a taktéž užitečnými technologiemi. Připomeňme si, že se jednalo jak o balíčky (knihovny) určené pro jazyk Python, tak i o rozšíření samotného Pythonu o nové konstrukce realizované klíčovými slovy async a await, které jsou typicky zkombinovány s již existujícími klíčovými slovy def, with a for.

Poznámka: konstrukce async a await se (tedy v případě, že se zaměříme na ty známější jazyky) nejprve objevily v jazyku F# a posléze se rozšířily do C#, JavaScriptu, TypeScriptu, Pythonu atd. I když se sémantika mnohdy poněkud odlišuje, stále se jedná v oblasti souběžných úloh o dnes již de facto standardní konstrukce, což je ostatně jen dobře (již tak roztříštěný svět IT není zapotřebí zbytečně dále fragmentovat vymýšlením unikátně pojmenovaných konstrukcí).

12. Tři nejčastěji používané strategie

Všechna již popsaná technologická řešení můžeme zobecnit a následně rozdělit do tří kategorií podle toho, jak jsou vlastně souběžné a popř. i plně paralelně běžící úlohy realizovány:

  1. První způsob spočívá v tvorbě a spouštění souběžných a současně i potenciálně paralelně běžících úloh, přičemž každá úloha je realizována samostatným procesem (process) viditelným a řízeným přímo operačním systémem. Jednotlivé úlohy jsou sice vzájemně izolovány, ovšem mohou spolu komunikovat s využitím objektu typu Pipe. Konkrétně je tento problém řešen standardními knihovnami nazvanými multiprocessing a ProcessPoolExecutor (lze používat odděleně). Oddělení (isolation) je v porovnání s ostatními dvěma technologiemi nejlepší, ovšem nevýhodou jsou obecně větší nároky na systémové prostředky (několik běžících virtuálních strojů Pythonu).
  2. Druhý způsob není založen na samostatně běžících procesech, ale na vláknech (thread), které jsou vytvářeny a spouštěny v rámci jediného procesu (virtuálního stroje Pythonu). Vzájemná izolace jednotlivých úloh je v tomto případě menší a záleží vlastně jen na vývojáři, zda a jak zajistí přístup většího množství vláken do sdílených proměnných. Standardně se pro komunikaci mezi vlákny používají různé realizace front popř. zásobníků (Queue, LifoQueue, PriorityQueue a SimpleQueue). Práce s větším množstvím vláken je nabízena ve standardních knihovnách threading a taktéž ThreadPoolExecutor (opět lze používat odděleně).
  3. Třetí způsob nepoužívá ani samostatně běžící procesy ani (již méně samostatná) vlákna, ale vystačí si s využitím korutin (koroutines), přičemž v daný okamžik může souběžně běžet větší množství korutin, přičemž jedna korutina je aktivní. Operace s korutinami jsou realizovány přes již zmíněná klíčová slova async a await využívaná například standardní knihovnou asyncio s vlastní realizací asynchronní fronty a podporované dalšími knihovnami typu aiohttp. Kromě toho je práce s korutinami podporována i dalšími knihovnami, z nichž za zmínku stojí především knihovna Curio či ještě lépe navržená knihovna Trio, která z Curia částečně vychází.
Poznámka: Pipe zajišťuje obousměrnou komunikaci, Queue typicky komunikaci jednosměrnou (ovšem nijak ji negarantuje) a channel striktně jednosměrnou komunikaci.

13. Jaké řešení je tedy nejvhodnější?

Pravděpodobně není možné bez podrobnějších znalostí řešeného problému říci, která z výše zmíněných technologií je obecně nejlepší. Do značné míry totiž záleží na konkrétních požadavcích, které má vytvářená aplikace či služba splňovat. Pokud je například nutné řešit především mnoho souběžných I/O operací typu přístup k souborům, databázím, proudům (streams), komunikace přes HTTP atd. (což patří mezi typické úlohy programované v Pythonu), může být výhodné použít korutiny a například knihovnu Trio nebo dnes přece jen více standardní dvojici knihoven asyncio+aiohttp. Předností tohoto řešení je fakt, že v případě využití Tria má programátor široké možnosti řízení korutin, dokáže korektně reagovat na případné výjimky, které mohou v korutinách vzniknout atd. Na druhé straně toto řešení nezaručuje skutečně paralelní běh výpočtů atd.

MIF22 EARLY

Pokud je na druhou stranu nutné realizovat spíše výpočetně náročné úlohy (které lze do jisté míry paralelizovat), může se pro tento účel více hodit multiprocesing a (v menší míře) multithreading, který je ovšem v CPythonu (dnes asi nejpoužívanější implementace Pythonu) omezen kvůli existenci GILu (a aby nedošlo k mýlce – GIL v žádném případě neřeší korektní přístup ke sdíleným proměnným). Zcela subjektivně bych řekl, že multithreading je ze všech tří nabízených řešení pravděpodobně nejhůře reálně použitelnou a v mnoha ohledech i nebezpečnou technologií (vlákna nejsou na jedné straně oddělena, na straně druhé se špatně řídí a kontroluje jejich stav).

14. 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 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
       
76 trio15.py programátorem vyvolané výjimky v korutině https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio15.py
77 trio16.py zachycení výjimky typu TaskError https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio16.py
       
78 trio17_no_consumer.py chování Tria v případě, že existuje jen producent zpráv u kanálu s nulovou kapacitou https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio17_no_consumer.py
79 trio18_no_consumer_capacity.py chybějící konzument, použití komunikačního kanálu s nenulovou kapacitou https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio18_no_consumer_capa­city.py
80 trio19_no_consumer_no_wait.py zápis zprávy do kanálu synchronní operací send_nowait https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio19_no_consumer_no_wa­it.py
81 trio20_no_consumer_fail_after.py pokus o zápis do kanálu se specifikovanou maximální dobou čekání https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio20_no_consumer_fail_af­ter.py
82 trio21_no_producer.py chování Tria v případě, že existuje jen konzument zpráv u kanálu s nulovou kapacitou https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio21_no_producer.py
83 trio22_no_producer_B.py úprava předchozího příkladu – náhrada smyčky async for za while https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio22_no_producer_B.py
84 trio23_no_producer_no_wait.py synchronní příjem zpráv z kanálu (bez čekání) https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio23_no_producer_no_wa­it.py
85 trio24_no_producer_fail_after.py pokus o příjem zprávy z kanálu se specifikovanou maximální dobou čekání https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio24_no_producer_fail_af­ter.py
86 trio25_multiple_prod_cons.py větší množství producentů a konzumentů zpráv https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio25_multiple_prod_con­s.py
       
87 producer_consumer.go klasická úloha typu producent-konzument naprogramovaná v jazyku Go https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/producer_consumer.go
88 producer_consumer2.go vylepšená klasická úloha typu producent-konzument naprogramovaná v jazyku Go https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/producer_consumer2.go
89 producer_only.go producent, zprávy nejsou nikým konzumovány https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/producer_only.go
90 consumer_only.go konzument, zprávy nejsou nikým produkovány https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/consumer_only.go
91 producers_consumers.go větší množství producentů a konzumentů zpráv https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/producers_consumers.go
       
92 trio26_timeouts.py specifikace maximální doby čekání na zprávu popř. pro poslání zprávy do blokovaného kanálu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio26_timeouts.py
93 trio_26B_move_after.py specifikace maximální doby čekání na zprávu popř. pro poslání zprávy do blokovaného kanálu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio_26B_move_after.py
94 trio27_channel_close.py explicitní uzavření kanálu v korutině, reakce na uzavření kanálu dalšími korutinami https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio27_channel_close.py
95 trio28_multiple_channel_close.py problematika uzavření kanálu používaného větším množstvím korutin https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio28_multiple_channel_clo­se.py
96 trio29_multiple_channel_close.py vyřešení předchozího problému – „naklonování“ kanálu https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio29_multiple_channel_clo­se.py
97 trio31_locks.py využití zámků https://github.com/tisnik/most-popular-python-libs/blob/master/concurren­t/trio31_locks.py

15. Předchozí články z miniseriálu o souběžných úlohách v Pythonu

  1. Souběžné a paralelně běžící úlohy naprogramované v Pythonu
    https://www.root.cz/clanky/soubezne-a-paralelne-bezici-ulohy-naprogramovane-v-pythonu/
  2. Souběžné a paralelně běžící úlohy naprogramované v Pythonu (2)
    https://www.root.cz/clanky/soubezne-a-paralelne-bezici-ulohy-naprogramovane-v-pythonu-2/
  3. Souběžné a paralelně běžící úlohy naprogramované v Pythonu – Curio a Trio
    https://www.root.cz/clanky/soubezne-a-paralelne-bezici-ulohy-naprogramovane-v-pythonu-curio-a-trio/
  4. Souběžné a paralelně běžící úlohy naprogramované v Pythonu – knihovna Trio
    https://www.root.cz/clanky/soubezne-a-paralelne-bezici-ulohy-naprogramovane-v-pythonu-knihovna-trio/
  5. Souběžné a paralelně běžící úlohy naprogramované v Pythonu – knihovna Trio (2)
    https://www.root.cz/clanky/soubezne-a-paralelne-bezici-ulohy-naprogramovane-v-pythonu-knihovna-trio-2/

16. 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
  44. Lock (computer science)
    https://en.wikipedia.org/wi­ki/Lock_(computer_science)
  45. Zámek (informatika)
    https://cs.wikipedia.org/wi­ki/Z%C3%A1mek_(informatika)

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.