Threadsicherheit unter Python

Gestern brauchte ich auf die schnelle eine threadsichere Warteschlange unter Python. Hintergrund war, dass ein Programm auf einen Netzwerkport lauscht und von dort Daten entgegen nimmt, die anschließend in eine Warteschlange müssen. Warum? Die Daten müssen einem anderen Programm übergeben werden, das kann leider immer nur einmal gleichzeitig laufen und braucht pro Durchlauf gut 40 Sekunden. Allerdings können die Daten schneller als eine Portion alle 40 Sekunden eintreffen.

In diesem Zusammenhang möchte ich kurz auf zwei Objekte aus dem threading-Modul hinweisen: threading.Lock() und threading.Event().

Mit Threading.Lock() lässt sich eine „Sperre“ instanzieren die dazu da ist Codeabschnitte nur von einem Thread gleichzeitig abarbeiten zu lassen. Das Objekt hat die Methoden .aquire() und .release(). Wurde mit acquire() der Lock angefordert wartet (blockiert) jeder andere Thread der dies ebenfalls tun will so lange bis der erste Aufrufer den Lock mit .release() wieder frei gibt.

Threading.Event() hingegen bietet die Möglichkeit einen Thread auf einen Event warten zu lassen. .set() löst das Event aus, .clear() makiert es als beendet und .wait() lässt den Thread warten.
Das Beispiel mit der Warteschlange: wird ein neues Element der Warteschlange angefügt wird für den Thread der sie abarbeiten soll .set() ausgelöst, so weiß er, dass es etwas zu tun gibt. Der Thread prüft die Länge der Warteschlange, ist sie größer als 0 wird sie abgearbeitet.
Nachdem ein Element abgearbeitet wurde fängt die Schleife von vorne an. Ist die Länge der Warteschlange diesmal jedoch 0 (in der Zeit der Abarbeitung ist kein weiteres Element für die Warteschlange eingetroffen), ruft der Thread für das Event .clear() auf. Dieser Aufruf sorgt dafür, dass er bei dem nächsten .wait() (so am Ende der Schleife folgen sollte) so lange wartet bis wieder .set() aufgerufen wird.

Ein kleiner Beisipielcode:

import thread
import threading
import time

event = threading.Event()

queue = []

def add_item_to_queue(item):
    queue.append(item)
    event.set()

def queue_worker_thread():
    while True:
        print "workerthread runs..."
        if len(queue) == 0:
            print "Nothing more to do..."
            event.clear()
        else:
            print "There are %i items in queue" % (len(queue))
            item = queue[0]
            queue.remove(item)
            print "5 sec hard work..."
            time.sleep(5)
            print "Ready:", item
        event.wait()

if __name__ == "__main__":
    thread.start_new_thread(queue_worker_thread, ())

Und so sieht das dann in Aktion aus:

workerthread runs...
Nothing more to do
>>> add_item_to_queue("Foo")
workerthread runs...
There are 1 items in queue
5 sec hard work...
>>> add_item_to_queue("bar")
Ready: Foo
workerthread runs...
There are 1 items in queue
5 sec hard work...
>>> add_item_to_queue("keks")
Ready: bar
workerthread runs...
There are 1 items in queue
5 sec hard work...
>>> add_item_to_queue("Alfred")
>>> add_item_to_queue("Ursula")
Ready: keks
workerthread runs...
There are 2 items in queue
5 sec hard work...
Ready: Alfred
workerthread runs...
There are 1 items in queue
5 sec hard work...
>>> add_item_to_queue("Dagobert")
Ready: Ursula
workerthread runs...
There are 1 items in queue
5 sec hard work...
Ready: Dagobert
workerthread runs...
Nothing more to do
>>> add_item_to_queue("Keksfisch")
workerthread runs...
There are 1 items in queue
5 sec hard work...
Ready: Keksfisch
workerthread runs...
Nothing more to do

— der Würschtlmann

3 Gedanken zu “Threadsicherheit unter Python

    • Es dient als Schnittstelle zwischen einer Webapplikation und der Datenübernahme in die verarbeitende Software.
      Leider dauert das Verarbeiten immer eine Weile (zwischen 10 Sekunden und mehreren Minuten), aber ich habe natürlich keinen Einfluss darauf wie schnell die Daten über die Webapplikation herein kommen. Deshalb habe ich die komplett separiert und eine einfache Netwerkschnittstelle gebaut die einfach Python-Objekte entgegen nimmt und diese dann verarbeitet.

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s