MicroPython: Threads / _thread

Das MicroPython-Modul „_thread“ ist eine Implementierung für Multithreading. Multithreading ist die Fähigkeit eines Prozessors, mehrere Programmabläufe (Threads) wechselweise ausführen zu können. Speziell bei simultanem Multithreading ist ein Prozessor sogar in der Lage mehrere Threads zeitgleich abzuarbeiten. Ein Thread ist ein Code-Faden bzw. ein Programmablauf.

Wenn ein Prozessor (CPU) über mehrere Rechenkerne (Multi-Core) verfügt, dann ist mit entsprechender Software-Unterstützung durch ein Betriebssystem oder entsprechender Programmierung tatsächlich eine parallele Ausführung eines Programmcodes möglich.

Das Parallelisieren von Funktionen verlangt vom Programmierer eine völlig andere Denkweise. Während man bei einer reinen sequentiellen Verarbeitung von Programmcode (Zeile für Zeile) den Programmablauf aus dem Programmcode ablesen kann, wird bei der parallelen Verarbeitung der Programmablauf verschachtelt oder ist mehrfach vorhanden. Das kann man aus dem Programmcode nur noch bedingt herauslesen. Das muss man sich denken oder vorstellen können.

Threads mit RP2040

Der RP2040 ist ein Dual-Core-Mikrocontroller. MicroPython unterstützt die Ausführung eines einzelnen zusätzlichen Threads, der auf dem zweiten Kern ausgeführt wird.
Bei einem Mikrocontroller, wie dem Raspberry Pi Pico haben wir typischerweise kein Betriebssystem. Das heißt, wollen wir den zweiten Rechenkern des RP2040-Mikrocontrollers auf dem Pico nutzen, dann muss sich der Programmierer darum kümmern.

MicroPython-Modul „_thread“

Das MicroPython-Modul „_thread“ ermöglicht dem Programmierer die Nutzung eines zweiten Rechenkerns, wenn der vorhanden ist.
Das MicroPython-Modul „_thread“ gilt als experimentell. Der Einsatz in einer Produktivumgebung ist nicht zu empfehlen. Für einfache, nebenläufige Funktionen kann man „_thread“ sicherlich nutzen.

Bei der Verwendung von „_threads“ ist zu beachten, dass bei dem Port „rp2“ (für Raspberry Pi Pico) alle Programmzustände sorgfältig zwischen den Kernen synchronisiert werden müssen.

Programmcode: Blinkende LED

Das MicroPython-Modul „_thread“ kann mit einem einfachen „import _thread“ im Programmcode hinzugefügt werden.

In diesem Programmcode stellt die Funktion „blink“ eine blinkende LED dar. Die Funktion wird zur Ausführung an den zweiten Rechenkern ausgelagert.

Im anschließenden Hauptprogramm wird einfach ein Zählerwert jede Sekunde hochgezählt und der Wert ausgegeben. Wenn die LED blinkt, dann weiß man, dass das Multithreading funktioniert.

# Bibliotheken laden
import machine
import time
import _thread

# Initialisierung der Onboard-LED
led_onboard = machine.Pin(25, machine.Pin.OUT, value=0)

# Funktion: Blinkende LED
def blink():
    while True:
        # LED einschalten
        led_onboard.on()
        # halbe Sekunde warten
        time.sleep(1)
        # LED ausschalten
        led_onboard.off()
        # 1 Sekunde warten
        time.sleep(1)

# Funktion aufrufen und vom 2. Kern ausführen lassen
_thread.start_new_thread(blink, ())

# Hauptprogramm (Sekundenzähler mit Schleife)
count = 0
while True:
    time.sleep(1)
    count += 1
    print(count)

Hinweis: Natürlich, funktional liese sich eine blinkende LED mit einer Timer-Funktion besser realisieren. Den zweiten Kern mit dieser Aufgabe zu betrauen, ist nicht nötig. Allerdings ist das nur ein Beispiel. Denn auf diese Weise ist ersichtlich, dass durch die in Software realisierte blinkende LED, das Programm auf dem zweiten Rechenkern läuft.

Verbraucht die Nutzung des 2. Kerns mehr Strom?

Bei dem oben dargestellten Programmcode kam es zu keiner Änderung des Stromverbrauchs.
Der Stromverbrauch lag zwischen 0,019 und 0,021 A in Abhängigkeit der blinkenden LED. Das heißt, die Nutzung des zweiten Kerns verbraucht keinen zusätzlichen Strom.

Thread stoppen (und neu starten)

Im oberen Programmcode blinkt die LED vor sich hin. Doch was ist, wenn man diesen Thread unterbrechen will. Gibt es ein Stop-Kommando? Nein, das gibt es leider nicht. Man muss sich selber darum kümmern, was sich aber mit einer globalen Variable lösen lässt.

Der folgende Programmcode ist um die Möglichkeit eines Beenden der Programmausführung auf dem zweiten Kern erweitert. Nach 10 Sekunden wird der zweite Thread beendet und nach weiteren 10 Sekunden wird er wieder gestartet.

# Bibliotheken laden
import machine
import time
import _thread

# Initialisierung der Onboard-LED
led_onboard = machine.Pin(25, machine.Pin.OUT, value=0)

# Globales Flag zum Steuern des Threads
THREAD_ACTIVE = True

# Funktion: Blinkende LED
def blink():
    global THREAD_ACTIVE
print("Thread gestartet") while THREAD_ACTIVE: # LED einschalten led_onboard.on() # halbe Sekunde warten time.sleep(1) # LED ausschalten led_onboard.off() # 1 Sekunde warten time.sleep(1) print("Thread beendet") # Funktion aufrufen und vom 2. Kern ausführen lassen _thread.start_new_thread(blink, ()) # Hauptprogramm (Sekundenzähler mit Schleife) count = 0 while True: time.sleep(1) count += 1 print(count) if count == 10: THREAD_ACTIVE = False if count == 20: THREAD_ACTIVE = True _thread.start_new_thread(blink, ())

Hinweis: Es gibt noch eine Lösung mit einer Lock-Funktion. Das ist vermutlich die fundiertere Lösung. Allerdings ist das eine einfachere Lösung.

Alternative: asyncio

In der Regel wird eine Lösung mit „asyncio“ einfacher und stabiler sein. Außer man braucht echte Parallelität. Mit „asyncio“ muss der Programmierer die einzelnen Programmteile in separate Funktionen ausgliedern, damit sie verschachtelt ausgeführt werden können.

Weitere verwandte Themen:

Elektronik-Sets für das Hardware-nahe Programmieren

Der Elektronik-Kompendium Club ist ein Online-Treffpunkt für Hobby-Elektroniker und Maker.

Wir wollen:

  • Anregungen und Lösungen von echten Menschen bekommen
  • Menschen zusammenbringen und gemeinsam wachsen
  • Erfahrungen machen und teilen

Jetzt mehr erfahren und Mitglied werden

Elektronik-Kompendium Club Meeting

Elektronik-Sets für das Hardware-nahe Programmieren

Frag Elektronik-Kompendium.de