Python-Dokumentationszitate
Die kanonische Version dieser Antwort befindet sich jetzt bei der wiederholten Frage: Was sind die Unterschiede zwischen den Modulen threading und multiprocessing?
Ich habe die wichtigsten Python-Dokumentationszitate zu Process vs Threads und dem GIL hervorgehoben unter: Was ist das globale Interpreter-Lock (GIL) in CPython?
Prozess vs Thread Experimente
Ich habe ein wenig Benchmarking gemacht, um den Unterschied konkreter zu zeigen.
Im Benchmark habe ich die CPU- und IO-bindende Arbeit für verschiedene Threadanzahlen auf einer 8 Hyperthread CPU getaktet. Die Arbeit, die pro Thread bereitgestellt wird, ist immer die gleiche, sodass mehr Threads bedeutet, dass insgesamt mehr Arbeit geleistet wird.
Die Ergebnisse waren:
Plot-Daten.
Schlussfolgerungen:
-
für CPU-gebundene Arbeit ist multiprocessing immer schneller, vermutlich aufgrund des GIL
-
für IO-gebundene Arbeit sind beide genau gleich schnell
-
Threads skalieren nur bis etwa 4x anstatt der erwarteten 8x, da ich auf einem 8-Hyperthread-Gerät bin.
Vergleichen Sie das mit einer C-POSIX-CPU-gebundenen Arbeit, die die erwarteten 8x-Speedup erreicht: Was bedeuten 'real', 'user' und 'sys' in der Ausgabe von time(1)?
TODO: Ich kenne den Grund dafür nicht, es müssen andere Python-Ineffizienzen im Spiel sein.
Testcode:
#!/usr/bin/env python3
import multiprocessing
import threading
import time
import sys
def cpu_func(result, niters):
'''
Eine nutzlose CPU-gebundene Funktion.
'''
for i in range(niters):
result = (result * result * i + 2 * result * i * i + 3) % 10000000
return result
class CpuThread(threading.Thread):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class CpuProcess(multiprocessing.Process):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class IoThread(threading.Thread):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
class IoProcess(multiprocessing.Process):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
if __name__ == '__main__':
cpu_n_iters = int(sys.argv[1])
sleep = 1
cpu_count = multiprocessing.cpu_count()
input_params = [
(CpuThread, cpu_n_iters),
(CpuProcess, cpu_n_iters),
(IoThread, sleep),
(IoProcess, sleep),
]
header = ['nthreads']
for thread_class, _ in input_params:
header.append(thread_class.__name__)
print(' '.join(header))
for nthreads in range(1, 2 * cpu_count):
results = [nthreads]
for thread_class, work_size in input_params:
start_time = time.time()
threads = []
for i in range(nthreads):
thread = thread_class(work_size)
threads.append(thread)
thread.start()
for i, thread in enumerate(threads):
thread.join()
results.append(time.time() - start_time)
print(' '.join('{:.6e}'.format(result) for result in results))
GitHub-Upstream + Plot-Code im gleichen Verzeichnis.
Auf Ubuntu 18.10, Python 3.6.7, getestet auf einem Lenovo ThinkPad P51 Laptop mit CPU: Intel Core i7-7820HQ CPU (4 Kerne / 8 Threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512HAJQ-000L7 (3,000 MB/s).
Visualisieren Sie, welche Threads zu einem bestimmten Zeitpunkt ausgeführt werden
Dieser Beitrag https://rohanvarma.me/GIL/ hat mich gelehrt, dass Sie mit dem Ziel=
-Argument von threading.Thread
und demselben für multiprocessing.Process
einen Rückruf ausführen können, wann immer ein Thread geplant ist.
Dies ermöglicht es uns genau zu sehen, welche Threads zu welcher Zeit ausgeführt werden. Wenn dies gemacht wird, würden wir etwas wie das Folgende sehen (ich habe dieses bestimmte Diagramm erfunden):
+--------------------------------------+
+ Aktive Threads / Prozesse +
+-----------+--------------------------------------+
|Thread 1 |******** ************ |
| 2 | ***** *************|
+-----------+--------------------------------------+
|Prozess 1 |*** ************** ****** **** |
| 2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
+ Zeit --> +
+--------------------------------------+
was zeigen würde, dass:
- Threads durch das GIL vollständig serialisiert sind
- Prozesse parallel laufen können
9 Stimmen
Ich denke, das könnte im Allgemeinen nützlich sein: blogs.datalogics.com/2013/09/25/… Obwohl je nach Sprache interessante Dinge passieren können. Zum Beispiel sind laut Andrew Sledges Link die Python-Threads langsamer. Bei Java ist es genau umgekehrt, Java-Prozesse sind viel langsamer als Threads, weil für einen neuen Prozess eine neue JVM benötigt wird.
7 Stimmen
Keine der beiden Top-Antworten (aktuelle Top, zweite Antwort) behandelt das GIL in irgendeiner signifikanten Weise. Hier ist eine Antwort, die das GIL-Aspekt behandelt: stackoverflow.com/a/18114882/52074
0 Stimmen
@AndrasDeak können wir das umgekehrt schließen gemäß: meta.stackoverflow.com/questions/251938/…, da diese viel mehr Upvotes/Antworten hat?
3 Stimmen
@CiroSantilli der Grund, warum ich diese Richtung gewählt habe, ist, weil die Antworten auf diese Frage schrecklich sind. Die akzeptierte Antwort hat wenig Substanz, im Kontext von Python ist sie inakzeptabel. Die am meisten gevotete Antwort ist besser, aber es fehlt immer noch eine ordentliche Erklärung. Die akzeptierte Antwort des Duplikats hat eine ausführliche Erklärung von einem der besten Beitragenden (und Lehrer) im Tag, der tatsächlich erklärt, was die "GIL-Beschränkungen" sind und warum man entweder verwenden möchte. Ich würde es vorziehen, das Duplikat in dieser Richtung zu behalten. Ich glaube, wir haben darüber im Python Chat diskutiert, aber ich kann dort nach Meinungen fragen, wenn du möchtest.
0 Stimmen
Besonders, da nicht registrierte Benutzer von der doppelten Quelle zum Ziel umgeleitet werden, würde ich es hassen, wenn sie diesen Beitrag anstelle von abarnerts Antwort finden würden.
0 Stimmen
@AndrasDeak OK, ich werde meine Antwort dann kopieren, da sie die beste ist XD
2 Stimmen
@CiroSantilli ah, ich habe übersehen, dass du hier eine Antwort hast! Als ich sagte "die Antworten [...] sind schrecklich", habe ich natürlich die Anwesenden ausgeschlossen ;) Ich denke, es wäre viel besser, dort deine Antwort zu haben!