Multithread-Programmierung
Tats?chlich beh?lt der Thread nach der Erstellung nicht immer einen Status bei:
Neu erstellt
Ausführungsbereit. Warten auf Terminplanung
L?uft L?uft
Blockiert. Beim Warten, gesperrt, schlafend kann es zu Blockierungen kommen
Dead Death
Threads haben unterschiedliche Zust?nde und unterschiedliche Typen. Es kann grob unterteilt werden in:
Hauptthread
Unterthread
Daemon-Thread (Hintergrund-Thread)
Vordergrund-Thread
Eine kurze Einführung Danach beginnen wir mit der Betrachtung der spezifischen Codeverwendung.
1. Erstellung von Threads
Python bietet zwei Module für Multithread-Operationen, n?mlich Thread und Threading
Das erstere ist relativ Low-Level-Module werden für Vorg?nge auf niedrigerer Ebene verwendet und werden in der allgemeinen Entwicklung auf Anwendungsebene nicht h?ufig verwendet.
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import time import threading class MyThread(threading.Thread): def run(self): for i in range(5): print('thread {}, @number: {}'.format(self.name, i)) time.sleep(1) def main(): print("Start main threading") # 創(chuàng)建三個(gè)線程 threads = [MyThread() for i in range(3)] # 啟動三個(gè)線程 for t in threads: t.start() print("End Main threading") if __name__ == '__main__': main()
Laufergebnisse:
Start main threading thread Thread-1, @number: 0 thread Thread-2, @number: 0 thread Thread-3, @number: 0 End Main threading thread Thread-2, @number: 1 thread Thread-1, @number: 1 thread Thread-3, @number: 1 thread Thread-1, @number: 2 thread Thread-3, @number: 2 thread Thread-2, @number: 2 thread Thread-2, @number: 3 thread Thread-3, @number: 3 thread Thread-1, @number: 3 thread Thread-3, @number: 4 thread Thread-2, @number: 4 thread Thread-1, @number: 4
Beachten Sie, dass die Ausgabeergebnisse verschiedener Umgebungen hier definitiv unterschiedlich sind.
2. Thread-Zusammenführung (Join-Methode)
Aus den im obigen Beispiel gedruckten Ergebnissen geht hervor, dass der untergeordnete Thread nach dem Ende des Hauptthreads noch l?uft . Wir müssen also warten, bis der untergeordnete Thread beendet ist, bevor wir ihn beenden. Was sollen wir tun?
Zu diesem Zeitpunkt müssen Sie die Join-Methode verwenden.
Fügen Sie im obigen Beispiel einen Code wie folgt hinzu:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import time import threading class MyThread(threading.Thread): def run(self): for i in range(5): print('thread {}, @number: {}'.format(self.name, i)) time.sleep(1) def main(): print("Start main threading") # 創(chuàng)建三個(gè)線程 threads = [MyThread() for i in range(3)] # 啟動三個(gè)線程 for t in threads: t.start() # 一次讓新創(chuàng)建的線程執(zhí)行 join for t in threads: t.join() print("End Main threading") if __name__ == '__main__': main()
Aus den gedruckten Ergebnissen k?nnen Sie deutlich erkennen, dass es sich im Vergleich zu den gedruckten Ergebnissen im obigen Beispiel um den Hauptthread handelt wartet. Es endet, nachdem die Ausführung des untergeordneten Threads abgeschlossen ist.
Start main threading thread Thread-1, @number: 0 thread Thread-2, @number: 0 thread Thread-3, @number: 0 thread Thread-1, @number: 1 thread Thread-3, @number: 1 thread Thread-2, @number: 1 thread Thread-2, @number: 2 thread Thread-1, @number: 2 thread Thread-3, @number: 2 thread Thread-2, @number: 3 thread Thread-1, @number: 3 thread Thread-3, @number: 3 thread Thread-3, @number: 4 thread Thread-2, @number: 4 thread Thread-1, @number: 4 End Main threading
3. Thread-Synchronisierung und Mutex-Sperre
Die Verwendung des Thread-Ladens zum Abrufen von Daten führt normalerweise dazu, dass die Daten nicht synchron sind. Natürlich k?nnen wir die Ressource zu diesem Zeitpunkt sperren, das hei?t, der Thread, der auf die Ressource zugreift, muss die Sperre erhalten, bevor er darauf zugreifen kann.
Das Threading-Modul stellt uns eine Sperrfunktion zur Verfügung.
lock = threading.Lock()
Erwirb die Sperre im Thread
lock.acquire()
Nachdem die Verwendung abgeschlossen ist, müssen wir die Sperre unbedingt aufheben
lock.release()
Natürlich, um mehrere Anfragen dafür zu unterstützen Dieselbe Ressource im selben Thread, Python stellt eine Wiedereintrittssperre (RLock) zur Verfügung. RLock verwaltet intern eine Sperre und eine Z?hlervariable. Der Z?hler zeichnet die Anzahl der Erfassungen auf, sodass die Ressource mehrmals ben?tigt werden kann. Bis alle Erfassungen eines Threads freigegeben sind, k?nnen andere Threads die Ressource abrufen.
Wie erstellt man also eine Wiedereintrittssperre? Es ist auch eine Frage einer Codezeile:
r_lock = threading.RLock()
4. Bedingungsvariable
Praktische Sperren k?nnen eine Thread-Synchronisierung erreichen, in komplexeren Umgebungen ist dies jedoch erforderlich für den Sperrrichter umgesetzt werden. Python stellt Bedingungsobjekte bereit. Das Condition-Objekt kann verwendet werden, um Daten zu verarbeiten, nachdem bestimmte Ereignisse ausgel?st wurden oder bestimmte Bedingungen erfüllt sind. Zus?tzlich zur Erfassungsmethode und Freigabemethode des Lock-Objekts stellt Condition auch Warte- und Benachrichtigungsmethoden bereit. Der Thread erh?lt zun?chst eine Bedingungsvariablensperre. Wenn die Bedingungen nicht ausreichen, wartet der Thread. Wenn die Bedingungen erfüllt sind, wird der Thread ausgeführt und kann sogar andere Threads benachrichtigen. Andere Threads im Wartezustand werden die Bedingungen nach Erhalt der Benachrichtigung neu beurteilen.
Die Bedingungsvariable kann als verschiedene Threads angesehen werden, die nacheinander die Sperre erwerben. Wenn die Bedingung nicht erfüllt ist, kann sie so verstanden werden, als würden sie in einen Wartepool (Lock oder RLock) geworfen. Benachrichtigen Sie direkt andere Threads und beurteilen Sie den Zustand dann erneut. Dieser Vorgang wird kontinuierlich wiederholt, um komplexe Synchronisationsprobleme zu l?sen.
Dieses Modell wird h?ufig im Produzenten-Konsumenten-Modell verwendet. Schauen Sie sich insbesondere das folgende Beispiel für Online-Shopping-K?ufer und -Verk?ufer an:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import threading, time class Consumer(threading.Thread): def __init__(self, cond, name): # 初始化 super(Consumer, self).__init__() self.cond = cond self.name = name def run(self): # 確保先運(yùn)行Seeker中的方法 time.sleep(1) self.cond.acquire() print(self.name + ': 我這兩件商品一起買,可以便宜點(diǎn)嗎') self.cond.notify() self.cond.wait() print(self.name + ': 我已經(jīng)提交訂單了,你修改下價(jià)格') self.cond.notify() self.cond.wait() print(self.name + ': 收到,我支付成功了') self.cond.notify() self.cond.release() print(self.name + ': 等待收貨') class Producer(threading.Thread): def __init__(self, cond, name): super(Producer, self).__init__() self.cond = cond self.name = name def run(self): self.cond.acquire() # 釋放對瑣的占用,同時(shí)線程掛起在這里,直到被 notify 并重新占有瑣。 self.cond.wait() print(self.name + ': 可以的,你提交訂單吧') self.cond.notify() self.cond.wait() print(self.name + ': 好了,已經(jīng)修改了') self.cond.notify() self.cond.wait() print(self.name + ': 嗯,收款成功,馬上給你發(fā)貨') self.cond.release() print(self.name + ': 發(fā)貨商品') cond = threading.Condition() consumer = Consumer(cond, '買家(兩點(diǎn)水)') producer = Producer(cond, '賣家(三點(diǎn)水)') consumer.start() producer.start()
Ausgabe Die Ergebnisse sind wie folgt:
買家(兩點(diǎn)水): 我這兩件商品一起買,可以便宜點(diǎn)嗎 賣家(三點(diǎn)水): 可以的,你提交訂單吧 買家(兩點(diǎn)水): 我已經(jīng)提交訂單了,你修改下價(jià)格 賣家(三點(diǎn)水): 好了,已經(jīng)修改了 買家(兩點(diǎn)水): 收到,我支付成功了 買家(兩點(diǎn)水): 等待收貨 賣家(三點(diǎn)水): 嗯,收款成功,馬上給你發(fā)貨 賣家(三點(diǎn)水): 發(fā)貨商品
5. Kommunikation zwischen Threads
Wenn das Programm mehrere Threads enth?lt, müssen diese Threads zwangsl?ufig miteinander kommunizieren . Wie k?nnen wir also Informationen oder Daten sicher zwischen diesen Threads austauschen?
Der wahrscheinlich sicherste Weg, Daten von einem Thread an einen anderen zu senden, ist die Verwendung einer Warteschlange aus der Warteschlangenbibliothek. Erstellt ein Queue-Objekt, das von mehreren Threads gemeinsam genutzt wird, die mithilfe der Operationen put() und get() Elemente zur Warteschlange hinzufügen oder daraus entfernen.
# -*- coding: UTF-8 -*- from queue import Queue from threading import Thread isRead = True def write(q): # 寫數(shù)據(jù)進(jìn)程 for value in ['兩點(diǎn)水', '三點(diǎn)水', '四點(diǎn)水']: print('寫進(jìn) Queue 的值為:{0}'.format(value)) q.put(value) def read(q): # 讀取數(shù)據(jù)進(jìn)程 while isRead: value = q.get(True) print('從 Queue 讀取的值為:{0}'.format(value)) if __name__ == '__main__': q = Queue() t1 = Thread(target=write, args=(q,)) t2 = Thread(target=read, args=(q,)) t1.start() t2.start()
Das Ausgabeergebnis lautet wie folgt:
寫進(jìn) Queue 的值為:兩點(diǎn)水 寫進(jìn) Queue 的值為:三點(diǎn)水 從 Queue 讀取的值為:兩點(diǎn)水 寫進(jìn) Queue 的值為:四點(diǎn)水 從 Queue 讀取的值為:三點(diǎn)水 從 Queue 讀取的值為:四點(diǎn)水
Python stellt auch das Ereignisobjekt für die Kommunikation zwischen Threads bereit. Es handelt sich um ein vom Thread gesetztes Signalflag. Andere Threads warten, bis das Signal kontaktiert wird.
Das Event-Objekt implementiert einen einfachen Thread-Kommunikationsmechanismus. Es stellt Einstellungssignale, L?schsignale, Wartesignale usw. bereit, um die Kommunikation zwischen Threads zu realisieren.
Signal setzen
Verwenden Sie die set()-Methode von Event, um das Signal-Flag im Event-Objekt auf true zu setzen. Das Event-Objekt stellt die Methode isSe() bereit, um den Status seines internen Signalflags zu bestimmen. Bei Verwendung der set()-Methode des Event-Objekts gibt die isSet()-Methode true zurück
Signal l?schen
Verwenden Sie die clear()-Methode des Event-Objekts, um das zu l?schen Signalflag innerhalb des Event-Objekts, das hei?t, setzen Sie es auf false, nachdem Sie die Clear-Methode von Event verwendet haben, isSet(). Die Methode gibt ?false“ zurück
Warten
Die Wartemethode des Ereignisobjekts wird schnell ausgeführt und schlie?t die Rückgabe nur ab, wenn das interne Signal wahr ist. Wenn das interne Signalflag des Event-Objekts ?false“ ist, wartet die Wartemethode, bis es ?true“ ist, bevor sie zurückkehrt.
Beispiel:
# -*- coding: UTF-8 -*- import threading class mThread(threading.Thread): def __init__(self, threadname): threading.Thread.__init__(self, name=threadname) def run(self): # 使用全局Event對象 global event # 判斷Event對象內(nèi)部信號標(biāo)志 if event.isSet(): event.clear() event.wait() print(self.getName()) else: print(self.getName()) # 設(shè)置Event對象內(nèi)部信號標(biāo)志 event.set() # 生成Event對象 event = threading.Event() # 設(shè)置Event對象內(nèi)部信號標(biāo)志 event.set() t1 = [] for i in range(10): t = mThread(str(i)) # 生成線程列表 t1.append(t) for i in t1: # 運(yùn)行線程 i.start()
Das Ausgabeergebnis lautet wie folgt:
1 0 3 2 5 4 7 6 9 8
6. Hintergrundthread
Standardm??ig der Hauptthread Thread wird danach beendet, auch wenn der untergeordnete Thread nicht beitritt. Nachdem der Hauptthread beendet ist, wird der untergeordnete Thread weiterhin ausgeführt. Wenn Sie m?chten, dass der Hauptthread beendet wird und seine Unterthreads ebenfalls beendet und nicht mehr ausgeführt werden, müssen Sie die Unterthreads als Hintergrundthreads festlegen. Python stellt die setDeamon-Methode bereit.