Functies gelijktijdig uitvoeren met threading

Standaard Python scripts zijn feitelijk een bundeling van sequentiële opdrachten die elkaar, al dan niet na het verwerken van voorwaarden, opvolgen. In een eenvoudig script kan de komende handeling niet eerder starten voordat de vorige handeling afgerond is. Dit is misschien wat kort door de bocht, maar wat ik hiermee bedoel is dat als je een functie/handeling wil starten terwijl een functie/handeling nog niet klaar is, je hier iets speciaals voor moet doen. Met andere woorden je wilt dat handelingen binnen een script niet op elkaar wachten om hun taak uit te voeren. Dit kun je realiseren met 'threading'.

In deze tutorial laat ik zien hoe je een functie in een script kunt laten uitvoeren, terwijl het script doorloopt en de volgende functie al afhandelt.

Wat is threading?
Threading wordt gebruikt om meerdere threads (taken en functies) tegelijkertijd uit te voeren. Dit betekent niet dat ze op verschillende CPU’s worden uitgevoerd. Threads maken je programma NIET sneller als het al 100% CPU-tijd gebruikt.

Wat zijn threads?
Een thread verwijst naar een thread van uitvoering in een computerprogramma. Elk programma is een proces en heeft ten minste één thread die instructies voor dat proces uitvoert.

Wanneer we een Python-script uitvoeren, start een instantie van de Python-interpreter die onze code in de hoofdthread (hoofdprogramma) uitvoert. De hoofdthread is de standaardthread van een Python-proces.

We kunnen ons programma aanpassen om taken gelijktijdig uit te voeren, in dit geval zullen we dan nieuwe threads moeten aanmaken en uitvoeren.

Een Python-thread wordt geleverd door het onderliggende besturingssysteem. Wanneer we een nieuwe thread aanmaken en uitvoeren, zal Python het systeem aanroepen op het onderliggende besturingssysteem en vragen om een nieuwe thread te maken en de nieuwe thread te starten. Hier hebben we verder geen invloed op. Het besturingssysteem handelt dit af zodra het kan.

Naslag
Voor gedetailleerde informatie m.b.t. threads verwijs ik je naar de gids van Jason Brownlee waar hij op een eenvoudige wijze deze lastige materie uitlegt. Paul McWhorter heeft er een videoles aan gewijd waar hij helder uitlegt wat het is en hoe je het gebruikt.

Hieronder ga ik verder met mijn beknopte uitleg die ik op basis van Jasons gids overgenomen heb.

Aan de slag!
Python biedt de mogelijkheid om nieuwe threads aan te maken en te beheren via de klasse threading.Thread

Om een functie in een andere thread uit te voeren moet je het volgende doen:

  1. Maak een instantie van de klasse threading.Thread
  2. Geef de naam van de functie op via het argument “target
  3. Roep de start()-functie aan

Eerst moeten we dus een nieuwe instantie van de threading.Thread-klasse (class) maken en de functie specificeren die we in een nieuwe thread willen uitvoeren via het argument “target“. De functie noemen we “taak“.

...
# creëer een thread
thread = Thread(target=taak)

De functie die in een andere thread wordt uitgevoerd, kan argumenten (nodig) hebben. In dat geval kunnen ze worden opgegeven als een tuple (= een verzameling objecten gescheiden door komma’s) en worden doorgegeven aan het argument “args” van de threading.Thread class-constructor.

...
# creëer een thread
thread = Thread(target=task, args=(arg1, arg2))

We kunnen beginnen met het uitvoeren van de thread door de functie start() aan te roepen .
De functie start() keert onmiddellijk terug en het besturingssysteem zal de functie in een aparte thread uitvoeren zodra dit mogelijk is.

...
# run de thread
thread.start()

En dat is alles.

Zoals gezegd hebben we geen controle over wanneer de thread precies wordt uitgevoerd of welke CPU-kern deze zal uitvoeren. Beide zijn verantwoordelijkheden die op laag niveau worden afgehandeld door het onderliggende besturingssysteem.

Laten we eens kijken naar een uitgewerkt voorbeeld van het uitvoeren van een functie in een nieuwe thread.


Voorbeeld van het uitvoeren van een functie in een thread
Ten eerste definiëren we een functie die in een andere thread wordt uitgevoerd. We maken een eenvoudige functie aan die kort pauzeert en vervolgens iets afdrukt.

De functie kan elke naam hebben die we willen, in dit geval noemen we het “taak“.

# Een functie die even pauzeert en tekst afdrukt
def taak():
    # korte pauze
    sleep(1)
    # print een bericht
    print ("Deze tekst is van een andere thread")

Vervolgens maken we een instantie van de threading.Thread-class en specificeren de functienaam als het argument “target in de constructor.

...
# creëer een thread
thread = Thread(target=taak)

Eenmaal aangemaakt voeren we de thread uit die de functie in een nieuwe thread zal uitvoeren, zodra het besturingssysteem dat kan.

...
# run de thread
thread.start()

De functie start() blokkeert niet omdat we geen pauze ingebouwd hebben, wat betekent dat deze onmiddellijk terugkeert.

We kunnen expliciet wachten tot de nieuwe thread klaar is met uitvoeren door de functie join() aan te roepen. Dit is niet per se nodig, omdat de hoofdthread (hoofdprogramma) pas wordt afgesloten als de nieuwe thread is voltooid, maar het legt de functie van join() wel duidelijker uit.

...
# wacht tot de thread klaar is
print ("Wacht op de thread...")
thread.join()

Bovenstaande wordt inclusief de benodigde import hieronder samengevoegd waarmee het uitvoeren van een functie in een andere thread duidelijk wordt.

# Voorbeeld van een functie in een andere thread
from time import sleep
from threading import Thread
 
# Een functie die even pauzeert en tekst afdrukt
def taak():
    # korte pauze
    sleep(1)
    # print een bericht
    print ("Deze tekst is van een andere thread")
 
# creëer een thread
thread = Thread(target=taak)

# run de thread
thread.start()

# wacht tot de thread klaar is
print ("Wacht op de thread...")
thread.join()

Als je het voorbeeld uitvoert, wordt eerst de threading.Thread gemaakt en wordt vervolgens de functie start() aangeroepen. Hierdoor wordt de thread niet onmiddellijk gestart, maar kan het besturingssysteem de functie plannen om zo snel mogelijk uit te voeren.

De hoofdthread (hoofdprogramma) drukt vervolgens een bericht af dat wacht tot de thread is voltooid en roept vervolgens de functie join() aan om expliciet te wachten tot de nieuwe thread klaar is met uitvoeren.

Zodra de functie terugkeert, wordt de thread gesloten. De functie keert door join() dan terug en de hoofdthread wordt afgesloten.

In de terminal is het volgende te zien.

Wacht op de thread…
Dit bericht is van een andere thread


Voorbeeld van het uitvoeren van een functie in een thread met argumenten
We kunnen functies die argumenten aannemen in een andere thread uitvoeren .

Dit doen we door eerst onze functie taak() uit de vorige sectie bij te werken om twee argumenten aan te nemen, één voor de tijd in seconden om te pauzeren en de tweede voor het weergeven van een bericht.

# Een functie die even pauzeert en tekst afdrukt
def taak(pauze, bericht):
    # korte pauze
    sleep(pauze)
    # print een bericht
    print(bericht)

Het aanroepen van de threading.Thread-constructor wordt bijgewerkt om de twee argumenten (pauze, bericht) op te geven. Dit moet in de volgorde waarin onze taak()-functie ze als een tuple verwacht, via het argument “args“.

...
# creëer een thread
thread = Thread(target=taak, args=(1.5, "Nieuw bericht van een andere thread"))

Hieronder wordt alles samengevoegd waarmee het uitvoeren van de functie, die argumenten in een andere thread aanneemt, duidelijk wordt.

# voorbeeld van het runnen van een functie met argumenten in een andere thread
from time import sleep
from threading import Thread
 
# Een functie die even pauzeert en tekst afdrukt
def taak(pauze, bericht):
    # korte pauze
    sleep(pauze)
    # print een bericht
    print(bericht)
 
# creëer een thread
thread = Thread(target=taak, args=(1.5, "Nieuw bericht van een andere thread"))

# run de thread
thread.start()

# wacht tot de thread klaar is
print('Wacht op de thread...')
thread.join()

Als je het voorbeeld uitvoert, wordt de thread aangemaakt met de gespecificeerde functienaam en de argumenten voor de functie.

De thread wordt gestart en de functieblokken voor het meegegeven aantal seconden en het meegegeven bericht worden uitgevoerd.

In de terminal is de volgende uitput te zien.

Wacht op de thread
Nieuw bericht van een andere thread


Voorbeeld code
Op basis van bovenstaande heb ik een script geschreven met drie functies die ieder een led aansturen. De eerste led (rood) wordt aangestuurd vanuit het hoofdprogramma, de hoofdthread. De tweede led (groen) wordt vanuit de thread ‘taak‘ aangestuurd. Zodra deze functie afgerond is gaat de routine expliciet terug naar de hoofdthread vanwege join() en start vervolgens de derde led (geel).

De leds die je voor het script nodig hebt sluit je in serie aan met weerstanden (330Ω) aan de onderstaande pinnen/GPIO.

Aansluittabel van de leds op de Raspberry Pi

Na het opstarten van het script zie je naast meldingen in de terminal de leds als volgt oplichten.

1 – De rode led (pin13/GPIO27) knippert 5x. Dit wordt vanuit de hoofdthread aangestuurd.

2 – Tijdens het knipperen van de rode led licht de groene led (pin11/GPIO17) 10 seconden op. Dit gebeurt in de thread met de functie genaamd ’taak’. Na 5x knipperen gaat de rode led uit. De groene led blijft nog even aan totdat deze 10 seconden opgelicht heeft.

3 – Na 10 seconden gaat de groene led uit en start de gele led (pin15/GPIO 22) met knipperen. Dit gebeurt weer in de hoofdthread.

Als je het script bekijkt zie je duidelijk de functies die de leds aansturen, het hoofdprogramma en de manier waarop de thread aangemaakt en gestart wordt.

Have A Nice Day!

Geef als eerste een reactie

Laat een reactie achter

Het e-mailadres wordt niet gepubliceerd.


*