it-swarm.com.de

Multiprocessing vs Threading Python

Ich versuche die Vorteile von Multiprocessing gegenüber Threading zu verstehen. Ich weiß, dass Multiprocessing die globale Interpreter-Sperre umgeht, aber welche anderen Vorteile gibt es und kann Threading nicht das gleiche tun?

703
John

Das Modul threading verwendet Threads, das Modul multiprocessing verwendet Prozesse. Der Unterschied besteht darin, dass Threads im selben Speicherbereich ausgeführt werden, während Prozesse über einen separaten Speicher verfügen. Dies erschwert die gemeinsame Nutzung von Objekten zwischen Prozessen mit Multiprocessing. Da Threads denselben Speicher verwenden, müssen Vorsichtsmaßnahmen getroffen werden, oder zwei Threads schreiben gleichzeitig in denselben Speicher. Dafür ist die globale Interpretersperre vorgesehen.

Das Starten von Prozessen ist etwas langsamer als das Starten von Threads. Sobald sie laufen, gibt es keinen großen Unterschied.

619
Sjoerd

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

Multiprozessing

Vorteile

  • Separater Speicherplatz
  • Code ist normalerweise unkompliziert
  • Nutzt mehrere CPUs und Kerne
  • Vermeidet GIL-Einschränkungen für cPython
  • Beseitigt die meisten Anforderungen an Synchronisationsprimitive, es sei denn, Sie verwenden gemeinsam genutzten Speicher (stattdessen handelt es sich eher um ein Kommunikationsmodell für IPC).
  • Untergeordnete Prozesse können unterbrochen/beendet werden
  • Das Python multiprocessing -Modul enthält nützliche Abstraktionen mit einer Schnittstelle, die der von threading.Thread ähnelt.
  • Ein Muss mit cPython für die CPU-gebundene Verarbeitung

Nachteile

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

Einfädeln

Vorteile

  • Geringes Gewicht - geringer Speicherbedarf
  • Shared Memory - Erleichtert den Zugriff auf Status aus einem anderen Kontext
  • Ermöglicht die einfache Erstellung ansprechender Benutzeroberflächen
  • cPython C-Erweiterungsmodule, die die GIL ordnungsgemäß freigeben, werden parallel ausgeführt
  • Hervorragende Option für E/A-gebundene Anwendungen

Nachteile

  • cPython - unterliegt der GIL
  • Nicht unterbrechbar/tötbar
  • Wenn Sie einem Befehlswarteschlangen-/Nachrichtenpumpenmodell (unter Verwendung des Moduls Queue) nicht folgen, ist die manuelle Verwendung von Synchronisationsprimitiven eine Notwendigkeit (Entscheidungen sind für die Granularität des Sperrens erforderlich).
  • Code ist normalerweise schwerer zu verstehen und zu verstehen - das Potenzial für Rennbedingungen steigt dramatisch
759
Jeremy Brown

Die Aufgabe von Threading besteht darin, die Reaktion von Anwendungen zu ermöglichen. Angenommen, Sie haben eine Datenbankverbindung und müssen auf Benutzereingaben reagieren. Ohne Threading kann die Anwendung dem Benutzer nicht antworten, wenn die Datenbankverbindung ausgelastet ist. Durch Aufteilen der Datenbankverbindung in einen separaten Thread können Sie die Reaktion der Anwendung verbessern. Da sich beide Threads im selben Prozess befinden, können sie auch auf dieselben Datenstrukturen zugreifen - eine gute Leistung und ein flexibles Softwaredesign.

Beachten Sie, dass die App aufgrund der GIL nicht zwei Dinge gleichzeitig erledigt. Wir haben jedoch die Ressourcensperre für die Datenbank in einen separaten Thread gestellt, sodass die CPU-Zeit zwischen ihr und der Benutzerinteraktion umgeschaltet werden kann. Die CPU-Zeit wird zwischen den Threads aufgeteilt.

Multiprocessing ist für Zeiten gedacht, in denen Sie wirklich möchten, dass zu einem bestimmten Zeitpunkt mehr als eine Sache erledigt wird. Angenommen, Ihre Anwendung muss eine Verbindung zu 6 Datenbanken herstellen und für jedes Dataset eine komplexe Matrixtransformation durchführen. Das Einfügen jedes Jobs in einen separaten Thread kann ein wenig hilfreich sein, da eine Verbindung im Leerlauf eine gewisse CPU-Zeit benötigt. Die Verarbeitung würde jedoch nicht parallel erfolgen, da die GIL bedeutet, dass Sie immer nur die Ressourcen einer CPU verwenden . Indem Sie jeden Job in einen Multiprocessing-Prozess einbinden, kann jeder auf seiner eigenen CPU ausgeführt und mit voller Effizienz ausgeführt werden.

193
Simon Hibbs

Der entscheidende Vorteil ist die Isolation. Ein abstürzender Prozess wird andere Prozesse nicht zum Erliegen bringen, wohingegen ein abstürzender Thread wahrscheinlich andere Threads in Mitleidenschaft zieht.

40
Marcelo Cantos

Eine andere Sache, die nicht erwähnt wird, ist, dass es von dem Betriebssystem abhängt, das Sie verwenden, wenn es um Geschwindigkeit geht. In Windows sind Prozesse teuer, daher sind Threads in Windows besser, in Unix-Prozessen jedoch schneller als in ihren Windows-Varianten. Daher ist die Verwendung von Prozessen in Unix viel sicherer und schneller.

27
chrissygormley

Andere Antworten haben sich mehr auf den Aspekt Multithreading vs. Multiprocessing konzentriert, aber in python Global Interpreter Lock (Gil) muss berücksichtigt werden. Wenn mehr Nummer (sprich k) von Threads erstellt werden, erhöhen sie in der Regel die Performance nicht um k Mal, da es immer noch als Single-Thread-Anwendung ausgeführt wird. GIL ist eine globale Sperre, die alles sperrt und nur die Ausführung eines einzelnen Threads unter Verwendung eines einzelnen Kerns ermöglicht. Die Leistung steigt an Stellen, an denen C-Erweiterungen wie Numpy, Network, I/O verwendet werden, wo viel Hintergrundarbeit geleistet und GIL veröffentlicht wird.
Also wann einfädeln wird verwendet, gibt es nur einen einzelnen Thread auf Betriebssystemebene, während python Pseudo-Threads erstellt, die vollständig durch Threading selbst verwaltet werden, aber im Wesentlichen als ein einzelner Prozess ausgeführt werden. Zwischen diesen Pseudo-Threads findet eine Preemption statt. Wenn die CPU mit maximaler Kapazität läuft, möchten Sie möglicherweise auf Mehrfachverarbeitung umschalten.
Nun können Sie im Falle von in sich geschlossenen Ausführungsinstanzen stattdessen den Pool wählen. Wenn sich Daten überschneiden und Prozesse kommunizieren sollen, sollten Sie multiprocessing.Process verwenden.

18

Zitate aus der Python-Dokumentation

Ich habe die wichtigsten Python Dokumentationszitate zu Process vs Threads und der GIL unter folgender Adresse hervorgehoben: Was ist die globale Interpretersperre (GIL) in CPython?

Prozess-gegen-Thread-Experimente

Ich habe ein bisschen Benchmarking durchgeführt, um den Unterschied konkreter darzustellen.

Im Benchmark habe ich die CPU- und IO -Zeitmessung für verschiedene Anzahlen von Threads auf einer 8-Hyperthread -CPU festgelegt. Die pro Thread gelieferte Arbeit ist immer dieselbe, sodass mehr Threads mehr gelieferte Gesamtarbeit bedeuten.

Die Ergebnisse waren:

enter image description here

Plotdaten .

Schlussfolgerungen:

  • bei CPU-gebundener Arbeit ist die Mehrfachverarbeitung immer schneller, vermutlich aufgrund der GIL

  • für IO gebundene Arbeit. beide sind genau gleich schnell

  • die Threads skalieren nur bis zu 4x anstatt der erwarteten 8x, da ich mich auf einem 8-Hyperthread-Rechner befinde.

    Vergleichen Sie dies mit einer C POSIX-CPU-gebundenen Arbeit, die die erwartete 8-fache Geschwindigkeit 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 ins Spiel kommen.

Testcode:

#!/usr/bin/env python3

import multiprocessing
import threading
import time
import sys

def cpu_func(result, niters):
    '''
    A useless CPU bound function.
    '''
    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 __== '__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 + Plotten von Code im selben Verzeichnis .

Getestet unter Ubuntu 18.10, Python 3.6.7, in 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).

Visualisiere, welche Threads zu einem bestimmten Zeitpunkt laufen

In diesem Beitrag https://rohanvarma.me/GIL/ wurde mir beigebracht, dass Sie einen Rückruf ausführen können, wenn ein Thread mit dem Argument target= von threading.Thread geplant ist. und dasselbe für multiprocessing.Process.

Auf diese Weise können wir genau sehen, welcher Thread zu jedem Zeitpunkt ausgeführt wird. Wenn dies erledigt ist, sehen wir so etwas wie (ich habe dieses Diagramm erstellt):

+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+

das würde zeigen, dass:

  • threads werden von der GIL vollständig serialisiert
  • prozesse können parallel ablaufen

Wie in der Frage erwähnt, ist Multiprocessing in Python der einzige echte Weg, um echte Parallelität zu erreichen. Multithreading kann dies nicht erreichen, da das GIL verhindert, dass Threads parallel ausgeführt werden.

Infolgedessen ist Threading in Python möglicherweise nicht immer nützlich und kann sogar zu einer schlechteren Leistung führen, je nachdem, was Sie erreichen möchten. Wenn Sie beispielsweise eine CPU-gebundene Aufgabe ausführen, wie z. B. das Dekomprimieren von gzip-Dateien oder das 3D-Rendern (alles CPU-intensive), kann das Threading tatsächlich Ihre Arbeit behindern Leistung statt Hilfe. In einem solchen Fall sollten Sie Multiprocessing verwenden, da nur diese Methode parallel ausgeführt wird und die Verteilung des Gewichts der jeweiligen Aufgabe erleichtert. Da Multiprocessing das Kopieren des Speichers eines Skripts in jeden Unterprozess beinhaltet, kann dies zu Problemen bei größeren Anwendungen führen.

Multithreading wird jedoch nützlich, wenn Ihre Aufgabe IO-gebunden ist . Wenn zum Beispiel der größte Teil Ihrer Aufgabe das Warten auf API-Aufrufe umfasst, würden Sie Multithreading verwenden, weil Sie nicht einen anderen starten sollten Anfrage in einem anderen Thread, während Sie warten, anstatt Ihre CPU im Leerlauf sitzen zu lassen.

TL; DR

  • Multithreading ist gleichzeitig aktiv und wird für IO-gebundene Aufgaben verwendet
  • Multiprocessing erzielt echte Parallelität und wird für CPU-gebundene Aufgaben verwendet
14
Bolboa

MULTIPROCESSING

  • Multiprocessing fügt CPUs hinzu, um die Rechenleistung zu erhöhen.
  • Es werden mehrere Prozesse gleichzeitig ausgeführt.
  • Die Erstellung eines Prozesses ist zeitaufwändig und ressourcenintensiv.
  • Multiprocessing kann symmetrisch oder asymmetrisch sein.
  • Die Multiprocessing-Bibliothek in Python verwendet separaten Speicherplatz, mehrere CPU-Kerne, umgeht die GIL-Einschränkungen in CPython, untergeordnete Prozesse können beendet werden (z. B. Funktionsaufrufe im Programm) und sind viel einfacher zu verwenden.
  • Einige Einschränkungen des Moduls sind ein größerer Speicherbedarf und IPCs etwas komplizierter mit mehr Overhead.

MULTITHREADING

  • Durch Multithreading werden mehrere Threads eines Prozesses erstellt, um die Rechenleistung zu erhöhen.
  • Mehrere Threads eines einzelnen Prozesses werden gleichzeitig ausgeführt.
  • Das Erstellen eines Threads ist sowohl zeit- als auch ressourcenschonend.
  • Die Multithreading-Bibliothek ist leichtgewichtig, teilt sich den Speicher, ist für die ansprechende Benutzeroberfläche verantwortlich und wird gut für E/A-gebundene Anwendungen verwendet.
  • Das Modul kann nicht getötet werden und unterliegt der GIL.
  • Mehrere Threads befinden sich im selben Prozess im selben Bereich. Jeder Thread führt eine bestimmte Aufgabe aus und verfügt über einen eigenen Code, einen eigenen Stapelspeicher, einen Befehlszeiger und einen gemeinsamen Heapspeicher.
  • Wenn ein Thread einen Speicherverlust aufweist, können die anderen Threads und der übergeordnete Prozess beschädigt werden.

Beispiel für Multi-Threading und Multi-Processing mit Python

Python 3 bietet die Möglichkeit, parallele Tasks starten . Das erleichtert uns die Arbeit.

Es hat für Thread-Pooling und Prozess-Pooling .

Folgendes gibt einen Einblick:

ThreadPoolExecutor Beispiel

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in Zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __== '__main__':
    main()
4
Jeril

Prozess kann mehrere Threads haben. Diese Threads können sich den Speicher teilen und sind die Ausführungseinheiten innerhalb eines Prozesses.

Prozesse werden auf der CPU ausgeführt, sodass sich unter jedem Prozess Threads befinden. Prozesse sind einzelne Einheiten, die unabhängig voneinander ablaufen. Wenn Sie Daten oder Status für jeden Prozess freigeben möchten, können Sie ein Speicher-Tool wie Cache(redis, memcache), Files oder Database verwenden.

Als ich an der Universität lernte, stimmen die meisten der obigen Antworten. In PRACTICE auf verschiedenen Plattformen (immer mit Python) werden mehrere Threads erstellt, so dass ein Prozess erstellt wird. Der Unterschied besteht darin, dass sich mehrere Kerne die Last teilen, anstatt dass nur 1 Kern alles zu 100% verarbeitet. Wenn Sie also beispielsweise 10 Threads auf einem 4-Kern-PC erzeugen, erhalten Sie nur die 25% der CPU-Leistung !! Und wenn Sie 10 Prozesse erzeugen, endet die CPU-Verarbeitung bei 100% (wenn Sie keine anderen Einschränkungen haben). Ich bin kein Experte in all den neuen Technologien. Ich antworte mit eigenen realen Erfahrung Hintergrund

0
Alex