Hoofdstuk 11
Een voorproefje van async
Waarom de meeste WebSocket-servers async zijn
De meeste async def. Je scorebord niet, met opzet: met threads kon elk eerder hoofdstuk één verbinding tegelijk volgen met het soort asyncio, herschrijft één handler als asyncio goed leren vraagt een eigen cursus: Async Python, die alleen Python in Practice nodig heeft.
Deze les schrijft geen code. Ze vraagt wat je server uitgeeft terwijl zijn kijkers wachten, en wat de andere stijl daarvoor terugvraagt.
Wat je server uitgeeft
Sinds hoofdstuk 7 draait je scorebord op de synchrone server van de bibliotheek uit hoofdstuk 6, die elke verbinding een eigen thread geeft. De handler draait in die thread van de
Een run op een lokale computer, nog niet op de site, telde de threads in een serverproces. Zonder verbonden kijker draaide de synchrone server 2 threads: de hoofdthread en die van serve_forever. Met 20 stille kijkers die vanuit een ander proces verbonden waren, draaide hij er 62. Dat zijn er 60 meer voor 20 kijkers, drie per kijker: de thread waarin je handler draait, een die de bibliotheek gebruikt om de verbinding te lezen, en een voor haar
Je synchrone scorebord heeft 20 kijkers die allemaal op de volgende score wachten. Wat doet elk van hun handlerthreads?
Een kijker wacht vooral
Hoeveel van die tijd is werk? Neem een quizavond van twee uur met 40 scores, en stel dat het sturen van één snapshot naar één kijker ongeveer een milliseconde duurt. Dit blok doet de rekensom voor één kijker:
Het drukt busy 0.04 s of 7200 s: 0.0006 % af. De milliseconde is een ruwe schatting, en tien keer zoveel zou de kijker nog steeds bijna de hele avond stil laten. Wachten is de normale toestand van een kijker, zoals hoofdstuk 7 ontdekte toen het kijkers voor het eerst tussen scores door verbonden hield. Een thread per kijker wordt betaald gedurende alle 7200 seconden, terwijl het werk optelt tot minder dan een tiende seconde.
Waarom doet het ertoe dat een kijker ruim minder dan één procent van een quiz bezig is?
Eén lus, veel wachten
Je hebt de andere vorm al gezien. In Networks with Python bediende hoofdstuk 9 veel clients vanuit één thread met selectors: één aanroep, select, wachtte tot een willekeurige
Een async def definieert, en les 2 laat zien hoe. De standaard event loop van Python woont in de asyncio, en de bibliotheek websockets heeft er een server op gebouwd, websockets.asyncio.
Dezelfde lokale run telde ook de threads van die server: 1 met 20 stille kijkers, en 1 zonder. De afbeelding zet de twee vormen voor twintig kijkers boven elkaar. Tel de threads in elk, en zoek de ene kijker die iets doet.

In de bovenste helft heeft elk van twintig kijkers drie threads (die van zijn handler, een lezer en een keepalive), plus de twee van de server, en bijna allemaal wachten ze: 2 + 3 x 20 = 62 threads. In de onderste helft staan dezelfde twintig kijkers geparkeerd, en draait één thread de kijker die verder kan, hier kijker 3, voordat hij terugkeert naar de lus. Dat is in totaal 1 thread.
De prijs
De lus heeft één thread, dus hij draait één handler tegelijk. Dat werkt alleen als elke handler de controle teruggeeft aan de lus wanneer hij moet wachten. Een handler die op de oude manier wacht, met een aanroep die blokkeert zoals time.sleep of de Event.wait van de outbox, houdt de enige thread bezet, en elke andere kijker wacht mee tot die aanroep terugkeert. Les 3 laat dat gebeuren zien, met een telling.
Geen van beide stijlen is dus gewoon beter. Voor de vijf clients die deze cursus tegelijk heeft bediend, kosten threads weinig en kun je over één verbinding tegelijk redeneren. Een service met duizenden kijkers die vooral wachten, is de gebruikelijke reden om naar een lus over te stappen, omdat zoveel threads veel meer zouden kosten dan het werk dat ze doen. Dat is het gangbare
Welke situatie is de sterkste reden om het scorebord naar een event loop te verhuizen?
In de volgende les schrijf je je eerste coroutines en draai je ze met asyncio.run, op de server.