Zoeken…


Opmerkingen

De Python-ontwikkelaars hebben ervoor gezorgd dat de API tussen threading en multiprocessing vergelijkbaar is, zodat het schakelen tussen de twee varianten eenvoudiger is voor programmeurs.

De threading-module

from __future__ import print_function
import threading
def counter(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

t1 = threading.Thread(target=countdown,args=(10,))
t1.start()
t2 = threading.Thread(target=countdown,args=(20,))
t2.start()

In bepaalde implementaties van Python zoals CPython, wordt ware parallelliteit niet bereikt met behulp van threads vanwege het gebruik van wat bekend staat als de GIL of G lobal I nterpreter L ock.

Hier is een uitstekend overzicht van de gelijktijdigheid van Python:

Python concurrency door David Beazley (YouTube)

De multiprocessing-module

from __future__ import print_function
import multiprocessing


def countdown(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

if __name__ == "__main__":
    p1 = multiprocessing.Process(target=countdown, args=(10,))
    p1.start()

    p2 = multiprocessing.Process(target=countdown, args=(20,))
    p2.start()

    p1.join()
    p2.join()

Hier wordt elke functie in een nieuw proces uitgevoerd. Aangezien een nieuwe instantie van Python VM de code uitvoert, is er geen GIL en krijg je parallellisme op meerdere cores.

De Process.start methode lanceert dit nieuwe proces en uitvoeren van de functie doorgegeven in het target ruzie met de argumenten args . De methode Process.join wacht op het einde van de uitvoering van processen p1 en p2 .

De nieuwe processen worden anders gestart, afhankelijk van de versie van python en de plateform waarop de code wordt uitgevoerd, bijvoorbeeld :

  • Windows gebruikt spawn om het nieuwe proces te maken.
  • Met unix-systemen en versies eerder dan 3.3 worden de processen gemaakt met behulp van een fork .
    Merk op dat deze methode het POSIX-gebruik van vork niet respecteert en dus tot onverwacht gedrag leidt, vooral bij interactie met andere multiprocessing-bibliotheken.
  • Met het unix-systeem en versie 3.4+, kunt u ervoor kiezen om de nieuwe processen te starten met fork , forkserver of spawn met multiprocessing.set_start_method aan het begin van uw programma. forkserver en spawn methoden zijn langzamer dan forking, maar vermijd onverwacht gedrag.

POSIX vork gebruik :

Na een vork in een multithreaded programma, kan het kind veilig alleen async-signaal-veilige functies aanroepen totdat het execve roept.
( zie )

Met behulp van vork wordt een nieuw proces gestart met exact dezelfde status voor alle huidige mutex, maar alleen de MainThread wordt gestart. Dit is onveilig omdat dit kan leiden tot raceomstandigheden, bijvoorbeeld :

  • Als u een Lock in MainThread en deze doorgeeft aan een andere thread waarvan u veronderstelt dat deze op een bepaald moment wordt vergrendeld. Als de fork tegelijkertijd optreedt, begint het nieuwe proces met een vergrendelde vergrendeling die nooit wordt vrijgegeven, omdat de tweede thread niet bestaat in dit nieuwe proces.

Eigenlijk zou dit soort gedrag niet in pure python moeten voorkomen, omdat multiprocessing het goed afhandelt, maar als je in interactie bent met een andere bibliotheek, kan dit soort gedrag optreden, wat kan leiden tot een crash van je systeem (bijvoorbeeld met numpy / accelerated op macOS).

Gegevens doorgeven tussen multiprocessing-processen

Omdat gegevens gevoelig zijn wanneer ze tussen twee threads worden behandeld (denk dat gelijktijdig lezen en gelijktijdig schrijven met elkaar kunnen conflicteren, waardoor race-omstandigheden kunnen ontstaan), zijn een aantal unieke objecten gemaakt om het heen en weer doorgeven van gegevens tussen threads mogelijk te maken. Elke echt atomaire bewerking kan tussen threads worden gebruikt, maar het is altijd veilig om bij Wachtrij te blijven.

import multiprocessing
import queue
my_Queue=multiprocessing.Queue() 
#Creates a queue with an undefined maximum size
#this can be dangerous as the queue becomes increasingly large
#it will take a long time to copy data to/from each read/write thread

De meeste mensen zullen suggereren om bij het gebruik van de wachtrij de wachtrijgegevens altijd in een poging te plaatsen: behalve: blokkeren in plaats van leeg gebruiken. Voor toepassingen waarbij het niet uitmaakt of u een queue.Empty==True overslaat (gegevens kunnen in de wachtrij worden geplaatst terwijl de queue.Empty==True uit de wachtrij wordt queue.Empty==True aan queue.Empty==False ) is meestal beter om te lezen en schrijf toegang in wat ik een Iftry-blok noem, omdat een 'if'-instructie technisch beter presteert dan de uitzondering opvangt.

import multiprocessing
import queue
'''Import necessary Python standard libraries, multiprocessing for classes and queue for the queue exceptions it provides'''
def Queue_Iftry_Get(get_queue, default=None, use_default=False, func=None, use_func=False):
    '''This global method for the Iftry block is provided for it's reuse and 
standard functionality, the if also saves on performance as opposed to catching
 the exception, which is expencive.
        It also allows the user to specify a function for the outgoing data to use,
 and a default value to return if the function cannot return the value from the queue'''
        if get_queue.empty():
            if use_default:
                return default
        else:
            try:
                value = get_queue.get_nowait()
            except queue.Empty:
                if use_default:
                    return default
            else:
                if use_func:
                    return func(value)
                else:
                    return value
    def Queue_Iftry_Put(put_queue, value):
        '''This global method for the Iftry block is provided because of its reuse 
and 
standard functionality, the If also saves on performance as opposed to catching
 the exception, which is expensive.
        Return True if placing value in the queue was successful. Otherwise, false'''
        if put_queue.full():
            return False
        else:
            try:
                put_queue.put_nowait(value)
            except queue.Full:
                return False
            else:
                return True


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow