Multiprocessing versus Python threading

Ik probeer de voordelen te begrijpen van multiprocessingten opzichte van threading. Ik weet dat multiprocessingde Global Interpreter Lock omzeilt, maar welke andere voordelen zijn er, en kan threadingniet hetzelfde doen?


Antwoord 1, autoriteit 100%

De module threadinggebruikt threads, de module multiprocessinggebruikt processen. Het verschil is dat threads in dezelfde geheugenruimte worden uitgevoerd, terwijl processen afzonderlijk geheugen hebben. Dit maakt het een beetje moeilijker om objecten tussen processen te delen met multiprocessing. Aangezien threads hetzelfde geheugen gebruiken, moeten voorzorgsmaatregelen worden genomen, anders schrijven twee threads tegelijkertijd naar hetzelfde geheugen. Dit is waar het globale tolkslot voor is.

Spawning-processen zijn iets langzamer dan het spawnen van threads.


Antwoord 2, autoriteit 97%

Hier zijn enkele voor- en nadelen die ik heb bedacht.

Multiverwerking

Pluspunten

  • Aparte geheugenruimte
  • Code is meestal eenvoudig
  • Maakt gebruik van meerdere CPU’s & kernen
  • Vermijdt GIL-beperkingen voor cPython
  • Elimineert de meeste behoefte aan synchronisatieprimitieven, tenzij u gedeeld geheugen gebruikt (in plaats daarvan is het meer een communicatiemodel voor IPC)
  • Kinderprocessen zijn onderbreekbaar/vernietigbaar
  • Python multiprocessing-module bevat nuttige abstracties met een interface die veel lijkt op threading.Thread
  • Een must met cPython voor CPU-gebonden verwerking

Nadelen

  • IPC iets gecompliceerder met meer overhead (communicatiemodel vs. gedeeld geheugen/objecten)
  • Groter geheugengebruik

Threading

Pluspunten

  • Lichtgewicht – weinig geheugengebruik
  • Gedeeld geheugen – maakt toegang tot status vanuit een andere context gemakkelijker
  • Hiermee kunt u eenvoudig responsieve gebruikersinterfaces maken
  • cPython C-uitbreidingsmodules die de GIL correct vrijgeven, zullen parallel worden uitgevoerd
  • Geweldige optie voor I/O-gebonden applicaties

Nadelen

  • cPython – onderworpen aan de GIL
  • Niet te onderbreken/niet te stoppen
  • Als u geen opdrachtwachtrij/berichtenpompmodel volgt (met behulp van de Queue-module), wordt handmatig gebruik van synchronisatieprimitieven een noodzaak (beslissingen zijn nodig voor de granulariteit van vergrendeling)
  • Code is meestal moeilijker te begrijpen en goed te begrijpen – het potentieel voor race-omstandigheden neemt dramatisch toe

Antwoord 3, autoriteit 30%

Het is de taak van Threading om applicaties responsief te maken. Stel dat u een databaseverbinding heeft en u moet reageren op gebruikersinvoer. Zonder threading kan de toepassing, als de databaseverbinding bezet is, niet op de gebruiker reageren. Door de databaseverbinding op te splitsen in een aparte thread, kunt u de applicatie responsiever maken. Omdat beide threads zich in hetzelfde proces bevinden, hebben ze toegang tot dezelfde gegevensstructuren – goede prestaties, plus een flexibel softwareontwerp.

Merk op dat vanwege de GIL de app eigenlijk niet twee dingen tegelijk doet, maar wat we hebben gedaan, is de bronvergrendeling op de database in een aparte thread plaatsen, zodat de CPU-tijd kan worden geschakeld tussen deze en de gebruikersinteractie. CPU-tijd wordt verdeeld tussen de threads.

Multiprocessing is voor momenten waarop u echt meer dan één ding tegelijk wilt doen. Stel dat uw applicatie verbinding moet maken met 6 databases en een complexe matrixtransformatie moet uitvoeren op elke dataset. Elke taak in een aparte thread plaatsen kan een beetje helpen, want wanneer een verbinding inactief is, kan een andere wat CPU-tijd krijgen, maar de verwerking zou niet parallel worden uitgevoerd omdat de GIL betekent dat u alleen de bronnen van één CPU gebruikt . Door elke taak in een Multiprocessing-proces te plaatsen, kan elke taak op zijn eigen CPU worden uitgevoerd en op volledige efficiëntie worden uitgevoerd.


Antwoord 4, autoriteit 10%

Python-documentatiecitaten

De canonieke versie van dit antwoord staat nu op de dubbele vraag: Wat zijn de verschillen tussen de threading- en multiprocessing-modules?

Ik heb de belangrijkste citaten uit de Python-documentatie over Process vs Threads en de GIL uitgelicht op: Wat is de globale interpreter lock (GIL) in CPython?

Proces versus thread-experimenten

Ik heb wat benchmarking gedaan om het verschil concreter te laten zien.

In de benchmark heb ik CPU- en IO-gebonden werk getimed voor verschillende aantallen threads op een 8 hyperthreadCPU. Het geleverde werk per thread is altijd hetzelfde, zodat meer threads meer totaal geleverd werk betekent.

De resultaten waren:

Plotgegevens.

Conclusies:

  • voor CPU-gebonden werk is multiprocessing altijd sneller, vermoedelijk dankzij de GIL

  • voor IO-gebonden werk. beide zijn precies dezelfde snelheid

  • threads schalen slechts tot ongeveer 4x op in plaats van de verwachte 8x omdat ik op een 8 hyperthread-machine werk.

    Vergelijk dat met een C POSIX CPU-gebonden werk dat de verwachte 8x snellere snelheid bereikt: Wat betekenen ‘real’, ‘user’ en ‘sys’ in de output van tijd(1)?

    TODO: Ik weet de reden hiervoor niet, er moeten andere inefficiënties van Python in het spel zijn.

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 __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 + plotcode in dezelfde map.

Getest op Ubuntu 18.10, Python 3.6.7, in een Lenovo ThinkPad P51-laptop met CPU: Intel Core i7-7820HQ CPU (4 cores / 8 threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512HAJQ-000L7 (3.000 MB/s).

Visualiseer welke threads op een bepaald moment actief zijn

Dit bericht https://rohanvarma.me/GIL/heeft me geleerd dat je altijd kunt terugbellen een thread is gepland met het argument target=van threading.Threaden hetzelfde voor multiprocessing.Process.

Hierdoor kunnen we precies zien welke thread op elk moment wordt uitgevoerd. Als dit gedaan is, zouden we iets zien als (ik heb deze specifieke grafiek verzonnen):

           +--------------------------------------+
            + Active threads / processes           +
+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
            + Time -->                             +
            +--------------------------------------+

wat zou aantonen dat:

  • threads zijn volledig geserialiseerd door de GIL
  • processen kunnen parallel lopen

Antwoord 5, autoriteit 6%

Het belangrijkste voordeel is isolatie. Een crashend proces zal andere processen niet neerhalen, terwijl een crashende thread waarschijnlijk grote schade aanricht aan andere threads.


Antwoord 6, autoriteit 4%

Een ander ding dat niet wordt genoemd, is dat het afhangt van het besturingssysteem dat je gebruikt als het om snelheid gaat. In Windows zijn processen kostbaar, dus threads zouden beter zijn in Windows, maar in Unix zijn processen sneller dan hun Windows-varianten, dus het gebruik van processen in Unix is veel veiliger en snel te spawnen.


Antwoord 7, autoriteit 3%

Zoals vermeld in de vraag, is Multiprocessingin Python de enige echte manier om echt parallellisme te bereiken. Multithreadingkan dit niet bereiken omdat de GILvoorkomt dat threads parallel lopen.

Als gevolg hiervan is threading mogelijk niet altijd nuttig in Python en kan het zelfs leiden tot slechtere prestaties, afhankelijk van wat u probeert te bereiken. Als u bijvoorbeeld een CPU-gebondentaak uitvoert, zoals het decomprimeren van gzip-bestanden of 3D-rendering (alles wat CPU-intensief is), kan threading uw prestaties eerder belemmeren dan helpen. In zo’n geval zou je Multiprocessingwillen gebruiken, omdat alleen deze methode daadwerkelijk parallel loopt en helpt om het gewicht van de taak te verdelen. Dit kan enige overhead met zich meebrengen, aangezien Multiprocessinghet kopiëren van het geheugen van een script naar elk subproces inhoudt, wat problemen kan veroorzaken voor grotere applicaties.

Multithreadingwordt echter handig wanneer uw taak IO-gebondenis. Als het grootste deel van uw taak bijvoorbeeld het wachten op API-aanroepeninhoudt, zou u Multithreadinggebruiken, want waarom zou u niet een ander verzoek starten in een andere thread terwijl u wacht, in plaats van je CPU zit werkeloos toe.

TL;DR

  • Multithreadingis gelijktijdig en wordt gebruikt voor IO-gebondentaken
  • Multiprocessingbereikt echt parallellisme en wordt gebruikt voor CPU-gebondentaken

Antwoord 8, autoriteit 2%

Andere antwoorden waren meer gericht op het aspect multithreading versus multiprocessing, maar in Python moet Global Interpreter Lock (GIL) in aanmerking worden genomen. Als er meer (zeg k) threads worden gemaakt, zullen ze de prestaties over het algemeen niet k keer verhogen, omdat het nog steeds draait als een applicatie met één thread. GIL is een globaal slot dat alles vergrendelt en alleen een enkele thread-uitvoering mogelijk maakt met slechts een enkele kern. De prestaties nemen wel toe op plaatsen waar C-extensies zoals numpy, Network, I/O worden gebruikt, waar veel achtergrondwerk wordt gedaan en GIL wordt vrijgegeven.
Dus wanneer threadingwordt gebruikt, is er slechts één thread op besturingssysteemniveau, terwijl python pseudo-threads creëert die volledig worden beheerd door zelf threading, maar in wezen draaien als een enkel proces. Voorkoop vindt plaats tussen deze pseudo-threads. Als de CPU op maximale capaciteit draait, wilt u misschien overschakelen naar multiprocessing.
Nu kunt u in het geval van op zichzelf staande uitvoeringsexemplaren kiezen voor pool. Maar in het geval van overlappende gegevens, waarbij u processen wilt laten communiceren, moet u multiprocessing.Processgebruiken.


Antwoord 9, autoriteit 2%

MULTIVERWERKING

  • Multiprocessing voegt CPU’s toe om de rekenkracht te vergroten.
  • Meerdere processen worden gelijktijdig uitgevoerd.
  • Het maken van een proces is tijdrovend en arbeidsintensief.
  • Multiprocessing kan symmetrisch of asymmetrisch zijn.
  • De multiprocessing-bibliotheek in Python gebruikt afzonderlijke geheugenruimte, meerdere CPU-kernen, omzeilt GIL-beperkingen in CPython, onderliggende processen kunnen worden uitgeschakeld (bijv. functieaanroepen in het programma) en zijn veel gemakkelijker te gebruiken.
  • Sommige voorbehouden van de module zijn een grotere geheugenvoetafdruk en IPC is een beetje ingewikkelder met meer overhead.

multithreading

  • Multithreading creëert meerdere draden van een enkel proces om de computervermogen te vergroten.
  • Meerdere draden van een enkel proces worden gelijktijdig uitgevoerd.
  • Creatie van een draad is economisch in beide verstandstijd en hulpbronnen.
  • De multithreading-bibliotheek is lichtgewicht, deelt geheugen, verantwoordelijk voor responsieve UI en wordt goed gebruikt voor I / O-gebonden applicaties.
  • De module is niet vermoord en is onderworpen aan de GIL.
  • Meerdere threads leven in hetzelfde proces in dezelfde ruimte, elke thread zal een specifieke taak doen, zijn eigen code, eigen stapelgeheugen, instructiewijzer en het delen van heap delen.
  • Als een draad een geheugenlek heeft, kan het de andere draden en ouderproces beschadigen.

voorbeeld van multi-threading en multiprocessing met behulp van Python

Python 3 heeft de faciliteit van het lanceren van parallelle taken . Dit maakt ons werk gemakkelijker.

Het heeft voor draad pooling en Process Pooling .

Het volgende geeft een inzicht:

ThreadpoolExecuter Voorbeeld

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 __name__ == '__main__':
    main()

Antwoord 10

Het proces kan meerdere threads hebben. Deze threads kunnen geheugen delen en zijn de uitvoeringseenheden binnen een proces.

Processen draaien op de CPU, dus onder elk proces bevinden zich threads. Processen zijn individuele entiteiten die onafhankelijk van elkaar draaien. Als u gegevens of status tussen elk proces wilt delen, kunt u een geheugenopslagtool gebruiken, zoals Cache(redis, memcache), Filesof een Database.


Antwoord 11

Zoals ik op de universiteit heb geleerd, zijn de meeste antwoorden hierboven juist. In PRAKTIJK op verschillende platforms (altijd met python) eindigt het spawnen van meerdere threads als het spawnen van één proces. Het verschil is dat meerdere cores de belasting delen in plaats van dat slechts 1 core alles op 100% verwerkt. Dus als je bijvoorbeeld 10 threads spawnt op een 4-core pc, krijg je uiteindelijk maar 25% van het CPU-vermogen!! En als je 10 processen spawnt, zul je eindigen met de cpu-verwerking op 100% (als je geen andere beperkingen hebt). Ik ben geen expert in alle nieuwe technologieën. Ik antwoord met eigen ervaringsachtergrond

Other episodes