Python-Lernen-Multithreading

Multitasking kann durch mehrere Prozesse oder durch mehrere Threads innerhalb eines Prozesses abgeschlossen werden.

Wir haben bereits erwähnt, dass ein Prozess aus mehreren Threads besteht und ein Prozess mindestens einen Thread hat.

Da der Thread die Ausführungseinheit ist, die direkt vom Betriebssystem unterstützt wird, verfügen Hochsprachen normalerweise über eine integrierte Multithreading-Unterstützung, und Python ist keine Ausnahme. Darüber hinaus sind Python-Threads echte Posix-Threads und keine simulierten Threads.

Die Standardbibliothek von Python bietet zwei Module: _thread und Threading. _Thread ist ein Modul auf niedriger Ebene, und Threading ist ein Modul auf hoher Ebene, das _thread kapselt. In den meisten Fällen müssen wir nur das erweiterte Threading-Modul verwenden.

Um einen Thread zu starten, müssen Sie eine Funktion übergeben und eine Thread-Instanz erstellen. Anschließend müssen Sie start () aufrufen, um die Ausführung zu starten:

import time, threading

# 新线程执行的代码:

def loop():
	print('thread %s is running...' % threading.current_thread().name)
	n = 0
	while n < 5:
		n = n + 1
		print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
		print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

Die Ergebnisse sind wie folgt:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

Da jeder Prozess standardmäßig einen Thread startet, nennen wir diesen Thread den Hauptthread, und der Hauptthread kann einen neuen Thread starten. Das Threading-Modul von Python verfügt über eine Funktion current_thread (), die immer eine Instanz des aktuellen Threads zurückgibt. Der Name der Haupt-Thread-Instanz lautet MainThread, der Name des untergeordneten Threads wird beim Erstellen angegeben, und wir verwenden LoopThread, um den untergeordneten Thread zu benennen. Der Name wird nur zum Anzeigen beim Drucken verwendet und hat keine andere Bedeutung . Wenn der Name nicht verfügbar ist, benennt Python automatisch den Thread Thread-1, Thread-2 ...

Sperren

Der größte Unterschied zwischen Multithreading und Multiprozess besteht darin, dass im Multiprozess dieselbe Variable in jedem Prozess eine Kopie hat und sich nicht gegenseitig beeinflusst. Im Multithread werden alle Variablen von allen Threads gemeinsam genutzt, also von jedem Variablen können von jedem Thread geändert werden. Daher besteht die größte Gefahr beim Austausch von Daten zwischen Threads darin, dass mehrere Threads gleichzeitig eine Variable ändern und der Inhalt geändert wird. (Also brauche ein Schloss)

Schauen wir uns an, wie mehrere Thread-Kollegen eine Variable bearbeiten, um den Inhalt zu ändern:

import time, threading

# 假定这是你的银行存款:
balance = 0

def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(2000000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

Wir definieren einen gemeinsamen Variablenausgleich, der Anfangswert ist 0, und starten zwei Threads, zuerst speichern und dann abrufen. Theoretisch sollte das Ergebnis 0 sein, aber da die Thread-Planung vom Betriebssystem bestimmt wird, wenn t1, t2 abwechselnd ausgeführt werden Zeit, solange die Anzahl der Zyklen ausreicht, ist das Ergebnis der Waage nicht unbedingt 0.

Der Grund dafür ist, dass eine Anweisung in einer höheren Sprache mehrere Anweisungen enthält, wenn die CPU ausgeführt wird , selbst eine einfache Berechnung:

balance = balance + n

Es gibt auch zwei Schritte:

  1. Berechnen Sie balance + n und speichern Sie es in einer temporären Variablen.
  2. Weisen Sie den Wert der temporären Variablen dem Saldo zu.

Es kann gesehen werden als:

x = balance + n
balance = x

Da x eine lokale Variable ist, hat jeder der beiden Threads ein eigenes x . Wenn der Code normal ausgeführt wird: (Es stellt sich heraus, dass dies der Fall ist. Die Hochsprache teilt eine Anweisung zur Ausführung in mehrere Teile auf.)

初始值 balance = 0

t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1     # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1     # balance = 0

t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2     # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2     # balance = 0
    
结果 balance = 0

Aber t1 und t2 werden abwechselnd ausgeführt, wenn das Betriebssystem t1 und t2 in der folgenden Reihenfolge ausführt:

初始值 balance = 0

t1: x1 = balance + 5  # x1 = 0 + 5 = 5

t2: x2 = balance + 8  # x2 = 0 + 8 = 8
t2: balance = x2      # balance = 8

t1: balance = x1      # balance = 5
t1: x1 = balance - 5  # x1 = 5 - 5 = 0
t1: balance = x1      # balance = 0

t2: x2 = balance - 8  # x2 = 0 - 8 = -8
t2: balance = x2      # balance = -8

结果 balance = -8

Der Grund dafür ist, dass mehrere Anweisungen erforderlich sind, um das Gleichgewicht zu ändern. Wenn diese Anweisungen ausgeführt werden, kann der Thread unterbrochen werden, wodurch mehrere Threads den Inhalt desselben Objekts durcheinander bringen.

Wenn zwei Threads gleichzeitig einzahlen und abheben, ist der Kontostand möglicherweise falsch. Sie möchten definitiv nicht, dass Ihre Bankeinzahlung irgendwie negativ wird. Daher müssen wir sicherstellen, dass sich der andere Thread nicht ändert, wenn ein Thread den Kontostand ändert es. (Gesperrt)

Wenn wir sicherstellen möchten, dass die Bilanzberechnung korrekt ist, müssen wir change_it () eine Sperre geben. Wenn ein Thread beginnt, change_it () auszuführen, sagen wir, dass andere Threads change_it () nicht ausführen können, da der Thread die Sperre erhalten hat Gleichzeitig können Sie nur warten, bis die Sperre aufgehoben wird, und sie dann nach dem Erwerb der Sperre ändern. Da es nur eine Sperre gibt, egal wie viele Threads, kann höchstens ein Thread die Sperre gleichzeitig halten, sodass kein Änderungskonflikt besteht. Das Erstellen einer Sperre erfolgt durch Threading.Lock (): (Die Sperre wird vom Programmierer vereinbart, und atomare Operationen können die Sperre ignorieren. Was ist die atomare Operation in Python? )

balance = 0
lock = threading.Lock()

def run_thread(n):
	for i in range(100000):
		# 先要获取锁:
		lock.acquire()
		try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

Wenn mehrere Threads gleichzeitig lock.acquire () ausführen, kann nur ein Thread die Sperre erfolgreich abrufen und dann den Code weiter ausführen, und andere Threads warten weiter, bis die Sperre erreicht wird.

** Nachdem der Thread, der die Sperre erworben hat, aufgebraucht ist, muss die Sperre freigegeben werden. Andernfalls wartet der Thread, der auf die Sperre wartet, für immer und wird zu einem toten Thread. ** Also verwenden wir try ... endlich, um sicherzustellen, dass die Sperre aufgehoben wird. (Es sollte ein Deadlock sein)

Der Vorteil der Sperre besteht darin, dass sichergestellt wird, dass ein bestimmter Schlüsselcode nur von einem Thread von Anfang bis Ende vollständig ausgeführt werden kann. Natürlich gibt es viele Nachteile. Erstens wird die gleichzeitige Ausführung mehrerer Threads verhindert. Ein bestimmter Codeabschnitt, der enthält eine Sperre kann nur in einem einzelnen Thread-Modus ausgeführt werden. Die Implementierung wird die Effizienz erheblich reduzieren. Zweitens, da es mehrere Sperren geben kann, halten verschiedene Threads unterschiedliche Sperren und versuchen, die von der anderen Partei gehaltenen Sperren zu erhalten, was zu Deadlocks führen kann und dazu, dass mehrere Threads hängen bleiben, die weder ausgeführt noch beendet werden können. Das Betriebssystem kann nur gezwungen werden zu beenden.

Multi-Core-CPU

Wenn Sie leider eine Multi-Core-CPU haben, müssen Sie denken, dass Multi-Core in der Lage sein sollte, mehrere Threads gleichzeitig auszuführen.

Was passiert, wenn Sie eine Endlosschleife schreiben?

Öffnen Sie den Aktivitätsmonitor von Mac OS X oder den Task-Manager von Windows, um die CPU-Auslastung eines bestimmten Prozesses zu überwachen.

Wir können überwachen, dass ein Endlosschleifen-Thread eine CPU zu 100% belegt.

Wenn in einer Mehrkern-CPU zwei Endlosschleifen-Threads vorhanden sind, können Sie überwachen, dass 200% der CPU belegt sind, dh zwei CPU-Kerne belegen.

Wenn Sie alle Kerne der N-Core-CPU ausführen möchten, müssen Sie N Endlosschleifen-Threads starten.

Versuchen Sie, eine Endlosschleife in Python zu schreiben: (Ich versuche es nicht)

import threading, multiprocessing

def loop():
    x = 0
    while True:
        x = x ^ 1

for i in range(multiprocessing.cpu_count()):
    t = threading.Thread(target=loop)
    t.start()

Starten Sie N Threads mit der gleichen Anzahl von CPU-Kernen. Auf einer 4-Kern-CPU können Sie überwachen, dass die CPU-Auslastung nur 102% beträgt, dh nur ein Kern verwendet wird.

Wenn Sie jedoch C, C ++ oder Java verwenden, um dieselbe Endlosschleife neu zu schreiben, können Sie alle Kerne direkt ausführen. 4 Kerne werden zu 400% und 8 Kerne zu 800% ausgeführt. Warum funktioniert Python nicht?

** Da der Python-Thread ein echter Thread ist, der Interpreter jedoch Code ausführt, gibt es eine GIL-Sperre: Globale Interpreter-Sperre. Bevor ein Python-Thread ausgeführt wird, muss er zuerst die GIL-Sperre erhalten. Dann jedes Mal 100 Byte Code ausgeführt werden, Der Interpreter gibt die GIL-Sperre automatisch frei, sodass andere Threads die Möglichkeit haben, sie auszuführen. ** Diese globale GIL-Sperre sperrt tatsächlich den Ausführungscode aller Threads. Daher kann Multithreading in Python nur abwechselnd ausgeführt werden. Selbst wenn 100 Threads auf einer 100-Core-CPU ausgeführt werden, kann nur einer verwendet werden.

GIL ist ein Erbe des Designs des Python-Interpreters. Normalerweise ist der von uns verwendete Interpreter die offizielle Implementierung von CPython, und wir müssen wirklich Multi-Core verwenden, es sei denn, wir schreiben einen Interpreter ohne GIL neu.

In Python können Sie Multithreading verwenden, erwarten jedoch nicht, dass Multicore effektiv verwendet wird. Wenn Sie mehrere Kerne über mehrere Threads verwenden müssen, kann dies nur über C-Erweiterungen erreicht werden. Dies verliert jedoch die Einfachheit und Benutzerfreundlichkeit von Python.

Machen Sie sich jedoch keine allzu großen Sorgen. Obwohl Python kein Multi-Threading verwenden kann, um Multi-Core-Aufgaben zu erledigen, kann es Multi-Core-Aufgaben über mehrere Prozesse ausführen. Mehrere Python-Prozesse haben ihre eigenen unabhängigen GIL-Sperren, die sich nicht gegenseitig beeinflussen.

Zusammenfassung

Multithread-Programmierung hat komplexe Modelle und ist anfällig für Konflikte. Sperren müssen verwendet werden, um sie zu isolieren. Gleichzeitig müssen Sie auf Deadlocks achten.

Der Python-Interpreter ist mit globalen GIL-Sperren ausgestattet, sodass mehrere Threads nicht mehrere Kerne verwenden können. Multithread-Parallelität ist in Python ein wunderschöner Traum .

Ich denke du magst

Origin blog.csdn.net/qq_44787943/article/details/112590047
Empfohlen
Rangfolge