Thread-Sperre in Python
In diesem Tutorial werden verschiedene Methoden zur Verwendung einer Threadsperre in Python erläutert.
Racebedingung in Python
Eine Racebedingung ist ein Problem, das auftritt, wenn mehrere Threads versuchen, dieselbe gemeinsam genutzte Variable zu ändern. Alle Threads lesen gleichzeitig denselben Wert aus der gemeinsamen Variablen. Dann versuchen alle Threads, den Wert der gemeinsamen Variablen zu ändern. Die Variable speichert jedoch nur den Wert des letzten Threads, da sie den vom vorherigen Thread geschriebenen Wert überschreibt. In diesem Sinne gibt es einen Wettlauf zwischen allen Threads, welcher am Ende den Wert der Variablen ändert. Dieses Phänomen wird mit einem Beispiel im folgenden Code demonstriert.
from threading import Thread
counter = 0
def increase(by):
global counter
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
t1 = Thread(target=increase, args=(10,))
t2 = Thread(target=increase, args=(20,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
Ausgabe:
counter=10
counter=20
The final counter is 20
Wir haben eine globale gemeinsame Variable counter = 0
und zwei Threads t1
und t2
. Der Thread t1
versucht, den Wert von counter
um 10 zu erhöhen und der Thread t2
versucht, den Wert von counter
um 20 zu erhöhen. Im obigen Code führen wir beide Threads gleichzeitig aus und versuchen, den Wert zu ändern von Zähler
. Nach der obigen Logik sollte der Endwert von counter
den Wert 30 haben. Aber wegen der Race-Condition ist der counter
entweder 10 oder 20.
Thread-Sperre in Python
Die Thread-Sperre wird verwendet, um die Race-Bedingung zu verhindern. Die Thread-Sperre sperrt den Zugriff auf eine gemeinsam genutzte Variable, wenn sie von einem Thread verwendet wird, sodass kein anderer Thread darauf zugreifen kann, und entfernt dann die Sperre, wenn der Thread die gemeinsam genutzte Variable nicht verwendet, sodass die Variable anderen Threads zur Verarbeitung zur Verfügung steht. Die Lock
-Klasse innerhalb des Threading-Moduls wird verwendet, um eine Thread-Sperre in Python zu erstellen. Die Methode acquire()
wird verwendet, um den Zugriff auf eine gemeinsame Variable zu sperren, und die Methode release()
wird verwendet, um die Sperre aufzuheben. Die Methode release()
löst eine RuntimeError
-Ausnahme aus, wenn sie für eine entsperrte Sperre verwendet wird.
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
lock.acquire()
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock.release()
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
Ausgabe:
counter=10
counter=30
The final counter is 30
Wir haben eine global geteilte Variable counter=0
und zwei Threads t1
und t2
erstellt. Beide Threads zielen auf dieselbe Funktion increase()
ab. Die Funktion increase(by, lock)
benötigt zwei Parameter. Der erste Parameter ist der Betrag, um den counter
erhöht wird, und der zweite Parameter ist eine Instanz der Lock
-Klasse. Zusätzlich zu den vorherigen Deklarationen haben wir auch eine Instanz lock
der Lock
-Klasse innerhalb des threading
-Moduls von Python erstellt. Dieser Parameter lock
in der Funktion increase(by, lock)
sperrt den Zugriff auf die Variable counter
mit der Funktion lock.acquire()
, während sie von einem beliebigen Thread verändert wird, und entsperrt die Sperre mit lock.release()
-Funktion, wenn ein Thread die Variable counter
modifiziert hat. Der Thread t1
erhöht den Wert von counter
um 10 und der Thread t2
erhöht den Wert von counter
um 20.
Aufgrund der Thread-Sperre tritt die Race Condition nicht auf und der Endwert von counter
ist 30.
Thread-Sperre mit with lock:
in Python
Das Problem bei der vorherigen Methode besteht darin, dass wir jede gesperrte Variable sorgfältig entsperren müssen, wenn ein Thread die Verarbeitung abgeschlossen hat. Wenn es nicht richtig gemacht wird, wird nur der erste Thread auf unsere Shared Variable zugreifen, und kein anderer Thread erhält Zugriff auf die Shared Variable. Dieses Problem kann durch die Verwendung der Kontextverwaltung vermieden werden. Wir können with lock:
verwenden und all unseren kritischen Code in diesem Block platzieren. Dies ist eine viel einfachere Möglichkeit, Rennbedingungen zu verhindern. Der folgende Codeausschnitt zeigt die Verwendung von with lock:
, um die Race-Condition in Python zu verhindern.
from threading import Thread, Lock
counter = 0
def increase(by, lock):
global counter
with lock:
local_counter = counter
local_counter += by
counter = local_counter
print(f"counter={counter}")
lock = Lock()
t1 = Thread(target=increase, args=(10, lock))
t2 = Thread(target=increase, args=(20, lock))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"The final counter is {counter}")
Ausgabe:
counter=10
counter=30
The final counter is 30
Wir haben unseren Code zum Inkrementieren von counter
im with lock:
-Block platziert. Der Thread t1
erhöht den Wert von counter
um 10 und der Thread t2
erhöht den Wert von counter
um 20. Die Race-Bedingung tritt nicht auf und der Endwert von counter
ist 30. Außerdem , Wir brauchen uns keine Sorgen um das Entsperren der Schraubensicherung zu machen.
Beide Methoden machen ihren Job perfekt, d.h. beide Methoden verhindern das Auftreten der Race Condition, aber die zweite Methode ist der ersten weit überlegen, da sie uns die Kopfschmerzen beim Sperren und Entsperren von Thread-Locks erspart. Es ist auch viel sauberer zu schreiben und leichter zu lesen als die erste Methode.
Maisam is a highly skilled and motivated Data Scientist. He has over 4 years of experience with Python programming language. He loves solving complex problems and sharing his results on the internet.
LinkedIn