Ricerca…


Osservazioni

Gli sviluppatori Python hanno fatto in modo che l'API tra threading e multiprocessing sia simile, in modo che il passaggio tra le due varianti sia più semplice per i programmatori.

Il modulo di filettatura

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 alcune implementazioni di Python come CPython, il vero parallelismo non si ottiene usando i thread a causa dell'uso di ciò che è noto come GIL o G lobal I nterpreter L ock.

Ecco un'eccellente panoramica della concorrenza Python:

Concorrenza Python di David Beazley (YouTube)

Il modulo multiprocessing

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()

Qui, ogni funzione viene eseguita in un nuovo processo. Poiché una nuova istanza di Python VM sta eseguendo il codice, non esiste GIL e il parallelismo viene eseguito su più core.

Il metodo Process.start avvia questo nuovo processo ed esegue la funzione passata nell'argomento di target con gli argomenti args . Il metodo Process.join attende la fine dell'esecuzione dei processi p1 e p2 .

I nuovi processi vengono lanciati in modo diverso a seconda della versione di python e della piastraforma su cui è in esecuzione il codice, ad esempio :

  • Windows utilizza spawn per creare il nuovo processo.
  • Con i sistemi unix e la versione precedente alla 3.3, i processi vengono creati utilizzando una fork .
    Si noti che questo metodo non rispetta l'utilizzo POSIX della forcella e quindi porta a comportamenti imprevisti, specialmente quando si interagisce con altre librerie di multiprocessing.
  • Con il sistema unix e la versione 3.4+, è possibile scegliere di avviare i nuovi processi con fork , forkserver o spawn utilizzando multiprocessing.set_start_method all'inizio del programma. forkserver metodi di forkserver e spawn sono più lenti della forking ma evitano comportamenti imprevisti.

Utilizzo della forcella POSIX :

Dopo un fork in un programma multithread, il bambino può chiamare in modo sicuro solo le funzioni sicure del segnale asincrono fino al momento in cui chiama execve.
( vedi )

Usando fork, verrà lanciato un nuovo processo con lo stesso esatto stato per tutto il mutex corrente ma verrà lanciato solo il MainThread . Questo non è sicuro in quanto potrebbe portare a condizioni di gara ad esempio :

  • Se si usa un Lock in MainThread e lo si passa ad un altro thread che si suppone lo blocchi ad un certo punto. Se la fork contemporaneamente, il nuovo processo inizierà con un blocco bloccato che non verrà mai rilasciato poiché il secondo thread non esiste in questo nuovo processo.

In realtà, questo tipo di comportamento non dovrebbe verificarsi in Python puro poiché il multiprocessing gestisce correttamente, ma se si interagisce con altre librerie, questo tipo di comportamento può verificarsi, portando ad un arresto anomalo del sistema (ad esempio con numpy / accelerato su macOS).

Passaggio di dati tra processi di multiprocessing

Poiché i dati sono sensibili quando vengono gestiti tra due thread (si pensi che la lettura simultanea e la scrittura simultanea possano essere in conflitto tra loro, causando condizioni di competizione), è stata creata una serie di oggetti unici per facilitare il passaggio di dati avanti e indietro tra i thread. Qualsiasi operazione veramente atomica può essere utilizzata tra i thread, ma è sempre sicuro attenersi a Queue.

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

La maggior parte delle persone suggerirà che quando si usa la coda, si inseriscano sempre i dati della coda in una prova: tranne: blocco invece di usare vuoto. Tuttavia, per le applicazioni in cui non importa se si ignora un ciclo di scansione (i dati possono essere inseriti nella coda mentre si queue.Empty==True stati dalla queue.Empty==True to queue.Empty==False ) di solito è meglio mettere i read e scrivere l'accesso in quello che io chiamo un blocco di Iftry, perché una dichiarazione di "se" è tecnicamente più performante di quella che cattura l'eccezione.

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow