1029 Stimmen

Mehrfachverarbeitung gegen Gewindeschneiden Python

Ich versuche, die Vorteile von [multiprocessing](http://docs. python.org/library/multiprocessing) gegenüber threading zu verstehen. Ich weiß, dass multiprocessing das Global Interpreter Lock umgeht, aber welche anderen Vorteile gibt es noch und kann threading nicht dasselbe tun?

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?

981voto

Jeremy Brown Punkte 16950

Hier sind einige Vor- und Nachteile, die ich herausgefunden habe.

Multiprocessing

Vorteile

  • Eigener Speicherplatz
  • Der Code ist normalerweise unkompliziert
  • Nutzt die Vorteile von mehreren CPUs & Kernen
  • Vermeidet GIL-Beschränkungen für cPython
  • Beseitigt die meisten Bedürfnisse nach Synchronisierungsprimitiven, es sei denn, Sie verwenden gemeinsam genutzten Speicher (stattdessen ist es mehr ein Kommunikationsmodell für IPC)
  • Kindprozesse können unterbrochen/beendet werden
  • Das Python multiprocessing Modul enthält nützliche Abstraktionen mit einer Schnittstelle, die der von threading.Thread ähnelt
  • Erforderlich bei cPython für prozessorlastige Prozesse

Nachteile

  • IPC etwas komplizierter mit mehr Overhead (Kommunikationsmodell vs. gemeinsamer Speicher/Objekte)
  • Größerer Speicherbedarf

Threading

Vorteile

  • Leichtgewichtig - geringer Speicherbedarf
  • Gemeinsamer Speicher - erleichtert den Zugriff auf den Zustand aus einem anderen Kontext
  • Ermöglicht es Ihnen, leicht reaktionsfähige Benutzeroberflächen zu erstellen
  • cPython C-Erweiterungsmodule, die den GIL ordnungsgemäß freigeben, werden parallel ausgeführt
  • Tolle Option für I/O-lastige Anwendungen

Nachteile

  • cPython - unterliegt dem GIL
  • Nicht unterbrechbar/beendbar
  • Wenn kein Befehls-Warteschlangen/Message-Pump-Modell befolgt wird (Verwendung des Queue-Moduls), dann wird die manuelle Verwendung von Synchronisierungsprimitiven zur Notwendigkeit (Entscheidungen sind für die Feinabstimmung des Sperrens erforderlich)
  • Code ist normalerweise schwerer zu verstehen und richtig zu machen - das Potenzial für Wettlaufbedingungen nimmt dramatisch zu

2 Stimmen

Es könnte möglich sein, dass multiprocessing im Falle von Freilisten zu einem kleineren Speicherbedarf führt.

61 Stimmen

Für Multiprozesse: "Nutzt mehrere CPUs und Kerne effizient aus". Hat Threading auch diesen Vorteil?

118 Stimmen

@Deqing nein, das tut es nicht. In Python kann aufgrund des GIL (Global Interpreter Lock) ein einzelner Python-Prozess keine Threads parallel ausführen (mehrere Kerne nutzen). Er kann sie jedoch gleichzeitig ausführen (Kontextwechsel während I/O-gebundener Operationen).

900voto

Sjoerd Punkte 71416

Das threading-Modul verwendet Threads, das multiprocessing-Modul verwendet Prozesse. Der Unterschied besteht darin, dass Threads im selben Speicherbereich ausgeführt werden, während Prozesse über separaten Speicher verfügen. Dies erschwert es, Objekte zwischen Prozessen mit Multiprocessing zu teilen. Da Threads denselben Speicher verwenden, müssen Vorsichtsmaßnahmen getroffen werden, da sonst zwei Threads gleichzeitig in denselben Speicher schreiben. Dafür ist das global interpreter lock gedacht.

Das Erzeugen von Prozessen ist etwas langsamer als das Erzeugen von Threads.

223 Stimmen

Die GIL in cPython sichert nicht den Zustand Ihres Programms. Es schützt den Zustand des Interpreters.

54 Stimmen

Auch das Betriebssystem behandelt Prozessplanung. Die Threadbibliothek behandelt Threadplanung. Und Threads teilen sich die I/O-Planung - was ein Engpass sein kann. Prozesse haben unabhängige I/O-Planung.

4 Stimmen

Wie sieht es mit der IPC-Leistung des Multiprozessings aus? Für ein Programm, das häufig Objekte zwischen Prozessen teilen muss (z. B. über multiprocessing.Queue), wie sieht der Leistungsvergleich zur In-Process-Warteschlange aus?

271voto

Simon Hibbs Punkte 5641

Die Aufgabe des Threading besteht darin, Anwendungen reaktionsfähig zu machen. Angenommen, Sie haben eine Datenbankverbindung und müssen auf Benutzereingaben reagieren. Ohne Threading, wenn die Datenbankverbindung beschäftigt ist, wird die Anwendung nicht in der Lage sein, auf den Benutzer zu reagieren. Durch Trennung der Datenbankverbindung in einen separaten Thread können Sie die Anwendung reaktionsfähiger machen. Außerdem können, da beide Threads im selben Prozess sind, auf dieselben Datenstrukturen zugreifen - gute Leistung und ein flexibles Software-Design.

Beachten Sie, dass aufgrund der GIL die Anwendung nicht tatsächlich zwei Dinge gleichzeitig ausführt, sondern was wir getan haben, ist das Ressourcenschloss der Datenbank in einen separaten Thread zu verschieben, so dass die CPU-Zeit zwischen ihm und der Benutzerinteraktion gewechselt werden kann. Die CPU-Zeit wird zwischen den Threads verteilt.

Multiprocessing ist für Zeiten, in denen Sie wirklich möchten, dass mehr als eine Sache gleichzeitig geschieht. Angenommen, Ihre Anwendung muss eine Verbindung zu 6 Datenbanken herstellen und eine komplexe Matrixtransformation auf jedem Datensatz durchführen. Das Aufteilen jeder Aufgabe in einen separaten Thread könnte ein wenig helfen, weil wenn eine Verbindung untätig ist, eine andere etwas CPU-Zeit erhalten könnte, aber die Verarbeitung würde nicht parallel erfolgen, da aufgrund der GIL nur die Ressourcen eines CPUs genutzt werden. Durch das Platzieren jeder Aufgabe in einem Multiprozess-Verfahren kann jede einzelne auf ihrem eigenen CPU ausgeführt werden und mit voller Effizienz laufen.

2 Stimmen

"aber die Verarbeitung würde nicht parallel ausgeführt werden, weil der GIL bedeutet, dass Sie nur die Ressourcen einer CPU verwenden" GIL bei Multiprocessing, wie kommt es dazu .... ?

7 Stimmen

@NishantKashyap - Lies den Satz noch einmal, aus dem du dieses Zitat genommen hast. Simon spricht über die Verarbeitung mehrerer Threads - es geht nicht um Multiprozessing.

0 Stimmen

Bei Speicherdifferenzen handelt es sich im Hinblick auf die anfänglichen Kapitalkosten. OpEx (laufende) Threads können genauso hungrig sein wie Prozesse. Du hast die Kontrolle über beide. Behandle sie als Kosten.

112voto

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:

enter image description here

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

0 Stimmen

Re: "Threads skaliert nur etwa bis zu 4x anstelle der erwarteten 8x, da ich auf einer 8-Hyperthread-Maschine bin." Für CPU-gebundene Aufgaben sollte erwartet werden, dass eine 4-Kern-Maschine bei 4x endet. Hyperthreading hilft nur beim CPU-Kontextwechsel. (In den meisten Fällen ist nur der "Hype" effektiv. /Witz)

0 Stimmen

SO mag keine doppelten Antworten, daher sollten Sie wahrscheinlich in Betracht ziehen, diese Instanz der Antwort zu löschen.

8 Stimmen

@AndrasDeak Ich werde es hier stehen lassen, weil diese Seite ansonsten weniger gut wäre und bestimmte Links brechen würden und ich hart erarbeiteten Ruf verlieren würde.

53voto

Marcelo Cantos Punkte 173498

Der Hauptvorteil ist Isolierung. Ein abstürzender Prozess bringt keine anderen Prozesse zum Absturz, während ein abstürzender Thread wahrscheinlich Chaos mit anderen Threads anrichten wird.

6 Stimmen

Ich bin ziemlich sicher, dass das einfach falsch ist. Wenn ein Standard-Thread in Python endet, indem eine Ausnahme ausgelöst wird, passiert nichts, wenn Sie ihn beitreten. Ich habe meine eigene Unterklasse von Thread geschrieben, die die Ausnahme in einem Thread abfängt und sie auf dem Thread neu auslöst, der ihm beitritt, weil die Tatsache, dass sie einfach ignoriert wurde, wirklich schlecht war (führte zu anderen schwer zu findenden Fehlern). Ein Prozess würde sich genauso verhalten. Es sei denn, mit Absturz meinten Sie tatsächlich einen Absturz von Python und nicht das Werfen einer Ausnahme. Wenn Sie jemals einen Absturz von Python feststellen, ist dies definitiv ein Fehler, den Sie melden sollten. Python sollte immer Ausnahmen auslösen und niemals abstürzen.

11 Stimmen

@ArtOfWarfare Threads können viel mehr tun als nur eine Ausnahme auslösen. Ein Rogue-Thread kann durch fehlerhaften nativen oder ctypes-Code Speicherstrukturen überall im Prozess beschädigen, einschließlich der Python-Laufzeitumgebung selbst, und somit den gesamten Prozess beschädigen.

0 Stimmen

@von einem generischen Standpunkt aus betrachtet ist Marcelos Antwort umfassender. Wenn das System wirklich kritisch ist, sollte man sich niemals darauf verlassen, dass "Dinge wie erwartet funktionieren". Mit separaten Speicherbereichen muss ein Überlauf auftreten, um benachbarte Prozesse zu beeinträchtigen, was weniger wahrscheinlich ist als die von Marcelo dargelegte Situation.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X