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")
    # 创建三个线程
    threads = [MyThread() for i in range(3)]
    # 启动三个线程
    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")
    # 创建三个线程
    threads = [MyThread() for i in range(3)]
    # 启动三个线程
    for t in threads:
        t.start()
    # 一次让新创建的线程执行 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.

35192f0e58595b25a0c422efd13ef05.png

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):
        # 确保先运行Seeker中的方法
        time.sleep(1)
        self.cond.acquire()
        print(self.name + ': 我这两件商品一起买,可以便宜点吗')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 我已经提交订单了,你修改下价格')
        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()
        # 释放对琐的占用,同时线程挂起在这里,直到被 notify 并重新占有琐。
        self.cond.wait()
        print(self.name + ': 可以的,你提交订单吧')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 好了,已经修改了')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ': 嗯,收款成功,马上给你发货')
        self.cond.release()
        print(self.name + ': 发货商品')
cond = threading.Condition()
consumer = Consumer(cond, '买家(两点水)')
producer = Producer(cond, '卖家(三点水)')
consumer.start()
producer.start()

Ausgabe Die Ergebnisse sind wie folgt:

买家(两点水): 我这两件商品一起买,可以便宜点吗
卖家(三点水): 可以的,你提交订单吧
买家(两点水): 我已经提交订单了,你修改下价格
卖家(三点水): 好了,已经修改了
买家(两点水): 收到,我支付成功了
买家(两点水): 等待收货
卖家(三点水): 嗯,收款成功,马上给你发货
卖家(三点水): 发货商品

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):
    # 写数据进程
    for value in ['两点水', '三点水', '四点水']:
        print('写进 Queue 的值为:{0}'.format(value))
        q.put(value)
def read(q):
    # 读取数据进程
    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:

写进 Queue 的值为:两点水
写进 Queue 的值为:三点水
从 Queue 读取的值为:两点水
写进 Queue 的值为:四点水
从 Queue 读取的值为:三点水
从 Queue 读取的值为:四点水

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对象内部信号标志
        if event.isSet():
            event.clear()
            event.wait()
            print(self.getName())
        else:
            print(self.getName())
            # 设置Event对象内部信号标志
            event.set()
# 生成Event对象
event = threading.Event()
# 设置Event对象内部信号标志
event.set()
t1 = []
for i in range(10):
    t = mThread(str(i))
    # 生成线程列表
    t1.append(t)
for i in t1:
    # 运行线程
    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.

Weiter lernen
||
einreichenCode zurücksetzen