Come posso usare il threading in Python?


1281

Sto cercando di capire il threading in Python. Ho esaminato la documentazione e gli esempi, ma francamente, molti esempi sono eccessivamente sofisticati e non riesco a capirli.

Come si mostrano chiaramente le attività divise per il multi-threading?


31
Una buona discussione generale su questo argomento può essere trovata nel problema più difficile di Python di Jeff Knupp. In sintesi, sembra che il threading non sia per i principianti.
Matthew Walker,

112
ahah, tendo a pensare che il threading sia per tutti, ma i principianti non sono per il threading :))))))
Bohdan,

42
Giusto per segnalare che le persone dovrebbero leggere tutte le risposte poiché quelle successive sono probabilmente migliori poiché le nuove funzionalità linguistiche sono sfruttate da ...
Gwyn Evans

5
Ricorda di scrivere la tua logica principale in C e chiamarla tramite ctypes per sfruttare davvero il threading Python.
aaa90210

4
Volevo solo aggiungere che PyPubSub è un ottimo modo per inviare e ricevere messaggi per controllare il flusso di thread
ytpillai,

Risposte:


1418

Da quando questa domanda è stata posta nel 2010, c'è stata una vera semplificazione nel modo di fare il multithreading semplice con Python con mappa e pool .

Il codice seguente proviene da un articolo / post sul blog che dovresti assolutamente provare (nessuna affiliazione) - Parallelismo su una riga: un modello migliore per le attività di threading giornaliere . Riassumo di seguito: finisce per essere solo poche righe di codice:

from multiprocessing.dummy import Pool as ThreadPool
pool = ThreadPool(4)
results = pool.map(my_function, my_array)

Qual è la versione multithread di:

results = []
for item in my_array:
    results.append(my_function(item))

Descrizione

Map è una piccola funzione interessante e la chiave per iniettare facilmente parallelismo nel tuo codice Python. Per coloro che non hanno familiarità, la mappa è qualcosa di sollevato da linguaggi funzionali come Lisp. È una funzione che mappa un'altra funzione su una sequenza.

Map gestisce l'iterazione sulla sequenza per noi, applica la funzione e memorizza tutti i risultati in un pratico elenco alla fine.

Inserisci qui la descrizione dell'immagine


Implementazione

Le versioni parallele della funzione mappa sono fornite da due librerie: multiprocessing, e anche il suo step step poco conosciuto, ma altrettanto fantastico: multiprocessing.dummy.

multiprocessing.dummyè esattamente lo stesso del modulo multiprocessore, ma utilizza invece i thread ( una distinzione importante : utilizzare più processi per attività ad alta intensità di CPU; thread per (e durante) l'I / O ):

multiprocessing.dummy replica l'API del multiprocessing, ma non è altro che un wrapper attorno al modulo threading.

import urllib2
from multiprocessing.dummy import Pool as ThreadPool

urls = [
  'http://www.python.org',
  'http://www.python.org/about/',
  'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
  'http://www.python.org/doc/',
  'http://www.python.org/download/',
  'http://www.python.org/getit/',
  'http://www.python.org/community/',
  'https://wiki.python.org/moin/',
]

# Make the Pool of workers
pool = ThreadPool(4)

# Open the URLs in their own threads
# and return the results
results = pool.map(urllib2.urlopen, urls)

# Close the pool and wait for the work to finish
pool.close()
pool.join()

E i risultati dei tempi:

Single thread:   14.4 seconds
       4 Pool:   3.1 seconds
       8 Pool:   1.4 seconds
      13 Pool:   1.3 seconds

Passando più argomenti (funziona così solo in Python 3.3 e versioni successive ):

Per passare più matrici:

results = pool.starmap(function, zip(list_a, list_b))

O per passare una costante e un array:

results = pool.starmap(function, zip(itertools.repeat(constant), list_a))

Se si utilizza una versione precedente di Python, è possibile passare più argomenti tramite questa soluzione alternativa ).

(Grazie a user136036 per il commento utile.)


90
Mancano solo voti perché è stato pubblicato di recente. Questa risposta funziona magnificamente e dimostra la funzionalità "mappa" che fornisce una sintassi molto più semplice da comprendere rispetto alle altre risposte qui.
inattivo il

25
Sono anche thread e non processi? Sembra che tenti di eseguire il multiprocesso! =
Multithread

72
A proposito, ragazzi, potete scrivere with Pool(8) as p: p.map( *whatever* )e sbarazzarvi anche delle righe di contabilità.

11
@BarafuAlbino: utile com'è, probabilmente vale la pena notare che funziona solo in Python 3.3+ .
Fuglede,

9
Come puoi lasciare questa risposta e non menzionare che è utile solo per le operazioni di I / O? Funziona solo su un singolo thread, il che è inutile nella maggior parte dei casi, ed è in realtà più lento del semplice farlo in modo normale
Frobot

714

Ecco un semplice esempio: devi provare alcuni URL alternativi e restituire il contenuto del primo per rispondere.

import Queue
import threading
import urllib2

# Called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Questo è un caso in cui il threading viene utilizzato come una semplice ottimizzazione: ogni sottotread è in attesa che un URL si risolva e risponda, al fine di mettere i suoi contenuti in coda; ogni thread è un demone (non continuerà il processo se il thread principale termina - è più comune che no); il thread principale avvia tutti i sottotread, fa un getin coda per aspettare fino a quando uno di loro ha fatto unput , quindi emette i risultati e termina (che elimina tutti i sottotread che potrebbero essere ancora in esecuzione, poiché sono thread daemon).

L'uso corretto dei thread in Python è invariabilmente collegato alle operazioni di I / O (poiché CPython non utilizza più core per eseguire comunque attività legate alla CPU, l'unico motivo per il threading non è bloccare il processo mentre si attende qualche I / O ). Le code sono quasi invariabilmente il modo migliore per distribuire il lavoro sui thread e / o raccogliere i risultati del lavoro, a proposito, e sono intrinsecamente sicuri per il thread, quindi ti evitano di preoccuparti di blocchi, condizioni, eventi, semafori e altri inter concetti di coordinamento / comunicazione.


10
Grazie ancora, MartelliBot. Ho aggiornato l'esempio per aspettare che tutti gli URL rispondano: import Queue, threading, urllib2 q = Queue.Queue () urls = '' ' a.com b.com c.com' ''. Split () urls_received = 0 def get_url (q, url): req = urllib2.Request (url) resp = urllib2.urlopen (req) q.put (resp.read ()) global urls_received urls_received + = 1 stampa urls_received for u in urls: t = threading.Thread (target = get_url, args = (q, u)) t.daemon = True t.start () mentre q.empty () e urls_received <len (urls): s = q.get () print s
htmldrum,

3
@JRM: se guardi la prossima risposta qui sotto, penso che un modo migliore di aspettare fino al termine dei thread sarebbe quello di utilizzare il join()metodo, dal momento che farebbe attendere il thread principale fino a quando non saranno completati senza consumare il processore costantemente verifica del valore. @Alex: grazie, questo è esattamente ciò di cui avevo bisogno per capire come usare i thread.
krs013,

6
Per python3, sostituisci "import urllib2" con "import urllib.request as urllib2". e inserire le parentesi nell'istruzione print.
Harvey,

5
Per python 3 sostituire il Queuenome del modulo con queue. Il nome del metodo è lo stesso.
JSmyth,

2
Prendo atto che la soluzione stamperà solo una delle pagine. Per stampare entrambe le pagine dalla coda è sufficiente eseguire nuovamente il comando: s = q.get() print s @ krs013 Non è necessario joinperché Queue.get () sta bloccando.
Tom Anderson,

256

NOTA : per l'effettiva parallelizzazione in Python, è necessario utilizzare il multiprocessing modulo per fork più processi che vengono eseguiti in parallelo (a causa del blocco dell'interprete globale, i thread Python forniscono interleaving, ma in realtà vengono eseguiti in serie, non in parallelo, e sono solo utile durante le operazioni di I / O interleaving).

Tuttavia, se stai semplicemente cercando interleaving (o stai eseguendo operazioni di I / O che possono essere parallelizzate nonostante il blocco dell'interprete globale), il modulo di threading è il punto di partenza. A titolo di esempio molto semplice, consideriamo il problema di sommare un ampio intervallo sommando subrange in parallelo:

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i


thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Si noti che quanto sopra è un esempio molto stupido, in quanto non ha assolutamente I / O e verrà eseguito in serie sebbene interfogliato (con l'overhead aggiuntivo del cambio di contesto) in CPython a causa del blocco dell'interprete globale.


16
@Alex, non ho detto che fosse pratico, ma dimostra come definire e generare thread, che penso sia ciò che l'OP vuole.
Michael Aaron Safyan,

6
Sebbene ciò mostri come definire e generare thread, in realtà non somma i sottorange in parallelo. thread1viene eseguito fino a quando non viene completato mentre il thread principale si blocca, quindi accade la stessa cosa thread2, quindi il thread principale riprende e stampa i valori accumulati.
martineau,

Non dovrebbe essere super(SummingThread, self).__init__()? Come in stackoverflow.com/a/2197625/806988
James Andres il

@JamesAndres, supponendo che nessuno erediti da "SummingThread", quindi uno dei due funziona bene; in tal caso super (SummingThread, self) è solo un modo elaborato per cercare la classe successiva nell'ordine di risoluzione del metodo (MRO), che è threading. Thread (e quindi successivamente chiamare init su quello in entrambi i casi). Hai ragione, però, che usare super () è uno stile migliore per l'attuale Python. Super era relativamente recente al momento in cui ho fornito questa risposta, quindi ha chiamato direttamente la super classe piuttosto che usare super (). Aggiornerò questo per usare super, però.
Michael Aaron Safyan,

14
ATTENZIONE: non utilizzare il multithreading in attività come questa! Come è stato dimostrato da Dave Beazley: dabeaz.com/python/NewGIL.pdf , 2 thread Python su 2 CPU eseguono un compito pesante per la CPU 2 volte più LENTO di 1 thread su 1 CPU e 1,5 volte più LENTO di 2 thread su 1 CPU. Questo comportamento bizzarro è dovuto al cattivo coordinamento degli sforzi tra OS e Python. Un caso d'uso reale per i thread è un compito pesante di I / O. Ad esempio, quando si eseguono operazioni di lettura / scrittura sulla rete, ha senso inserire un thread, in attesa che i dati vengano letti / scritti, in background e passare alla CPU su un altro thread, che deve elaborare i dati.
Boris Burkov,

98

Come altri menzionati, CPython può usare i thread solo per le attese di I / O dovute a GIL .

Se si desidera beneficiare di più core per attività associate alla CPU, utilizzare il multiprocessing :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

33
potresti spiegarci un po 'cosa fa?
Pandita,

5
@pandita: il codice crea un processo, quindi lo avvia. Quindi ora stanno accadendo due cose contemporaneamente: la linea principale del programma e il processo che inizia con l'obiettivo, la ffunzione. Parallelamente, il programma principale ora aspetta solo che il processo termini, joinfinendo con esso. Se la parte principale è appena uscita, il sottoprocesso potrebbe o non potrebbe essere eseguito fino al completamento, pertanto joinè sempre consigliabile eseguire una .
johntellsall,

1
Una risposta estesa che include la mapfunzione è qui: stackoverflow.com/a/28463266/2327328
philshem

2
@philshem Fai attenzione perché il link che hai pubblicato utilizza un pool di thread (non processi) come indicato qui stackoverflow.com/questions/26432411/… . Tuttavia, questa risposta utilizza un processo. Sono nuovo di queste cose, ma sembra che (grazie a GIL) otterrai miglioramenti delle prestazioni solo in situazioni specifiche quando usi il multithreading in Python. Tuttavia, l'utilizzo di un pool di processi può trarre vantaggio da un processore multicore avendo più di 1 core lavoro su un processo.
user3731622,

3
Questa è la risposta migliore per fare effettivamente qualcosa di utile e trarre vantaggio da più core della CPU
Frobot

92

Solo una nota: non è richiesta una coda per il threading.

Questo è l'esempio più semplice che potrei immaginare che mostra 10 processi in esecuzione contemporaneamente.

import threading
from random import randint
from time import sleep


def print_number(number):

    # Sleeps a random 1 to 10 seconds
    rand_int_var = randint(1, 10)
    sleep(rand_int_var)
    print "Thread " + str(number) + " slept for " + str(rand_int_var) + " seconds"

thread_list = []

for i in range(1, 10):

    # Instantiates the thread
    # (i) does not make a sequence, so (i,)
    t = threading.Thread(target=print_number, args=(i,))
    # Sticks the thread in a list so that it remains accessible
    thread_list.append(t)

# Starts threads
for thread in thread_list:
    thread.start()

# This blocks the calling thread until the thread whose join() method is called is terminated.
# From http://docs.python.org/2/library/threading.html#thread-objects
for thread in thread_list:
    thread.join()

# Demonstrates that the main process waited for threads to complete
print "Done"

3
Aggiungi l'ultima citazione a "Fatto per farlo stampare" Fatto "
iChux l'

1
Mi piace questo esempio meglio di quello di Martelli, è più facile giocarci. Tuttavia, consiglierei a printNumber di fare quanto segue, per rendere un po 'più chiaro cosa sta succedendo: dovrebbe salvare il randint in una variabile prima di dormire su di esso, e quindi la stampa dovrebbe essere cambiata per dire "Thread" + str ( numero) + "dormito per" + theRandintVariable + "seconds"
Nickolai

C'è un modo per sapere quando ogni thread è finito, come finisce?
Matt,

1
@Matt Ci sono alcuni modi per fare qualcosa del genere, ma dipende dalle tue esigenze. Un modo sarebbe quello di aggiornare un singleton o qualche altra variabile accessibile pubblicamente che viene guardata in un ciclo while e aggiornata alla fine del thread.
Douglas Adams,

2
Non è necessario il secondo forloop, è possibile chiamare thread.start()nel primo loop.
Mark Mishyn,

49

La risposta di Alex Martelli mi ha aiutato. Tuttavia, ecco una versione modificata che pensavo fosse più utile (almeno per me).

Aggiornato: funziona sia in Python 2 che in Python 3

try:
    # For Python 3
    import queue
    from urllib.request import urlopen
except:
    # For Python 2 
    import Queue as queue
    from urllib2 import urlopen

import threading

worker_data = ['http://google.com', 'http://yahoo.com', 'http://bing.com']

# Load up a queue with your data. This will handle locking
q = queue.Queue()
for url in worker_data:
    q.put(url)

# Define a worker function
def worker(url_queue):
    queue_full = True
    while queue_full:
        try:
            # Get your data off the queue, and do some work
            url = url_queue.get(False)
            data = urlopen(url).read()
            print(len(data))

        except queue.Empty:
            queue_full = False

# Create as many threads as you want
thread_count = 5
for i in range(thread_count):
    t = threading.Thread(target=worker, args = (q,))
    t.start()

6
Perché non rompere l'eccezione?
Stavros Korokithakis,

1
potresti, solo preferenze personali
JimJty

1
Non ho eseguito il codice, ma non è necessario demonizzare i thread? Penso che dopo l'ultimo for-loop, il tuo programma potrebbe uscire - almeno dovrebbe perché è così che dovrebbero funzionare i thread. Penso che un approccio migliore non sia quello di mettere i dati di lavoro nella coda, ma di mettere l'output in una coda perché allora potresti avere un mainloop che non solo gestisce le informazioni che entrano nella coda dai lavoratori, ma ora non sta anche eseguendo il threading, e sai che non uscirà prematuramente.
dylnmc,

1
@dylnmc, questo è al di fuori del mio caso d'uso (la mia coda di input è predefinita). Se vuoi seguire il tuo percorso, suggerirei di guardare il sedano
JimJty,

@JimJty sai perché sto ricevendo questo errore: import Queue ModuleNotFoundError: No module named 'Queue'sto eseguendo python 3.6.5 alcuni post menzionano che in python 3.6.5 lo è, queuema anche dopo che lo cambio, non funziona ancora
user9371654

25

Data una funzione, fesegui il thread in questo modo:

import threading
threading.Thread(target=f).start()

Per passare argomenti a f

threading.Thread(target=f, args=(a,b,c)).start()

Questo è molto semplice. Come assicurate che i thread si chiudano quando avete finito con loro?
Cameronroytaylor,

Per quanto ho capito, quando la funzione esce l' Threadoggetto pulisce. Vedi i documenti . C'è un is_alive()metodo che puoi usare per controllare un thread, se necessario.
Starfry,

Ho visto il is_alivemetodo, ma non sono riuscito a capire come applicarlo al thread. Ho provato ad assegnare thread1=threading.Thread(target=f).start()e quindi a verificarlo thread1.is_alive(), ma thread1è popolato con None, quindi non ho avuto fortuna. Sai se esiste un altro modo per accedere al thread?
Cameronroytaylor,

4
Devi assegnare l'oggetto thread a una variabile e quindi avviarlo usando quel file variabile: thread1=threading.Thread(target=f)seguito da thread1.start(). Quindi puoi farlo thread1.is_alive().
Starfry,

1
Ha funzionato E sì, test con thread1.is_alive()ritorni Falsenon appena la funzione esce.
Cameronroytaylor,

25

L'ho trovato molto utile: crea tanti thread quanti core e lascia che eseguano un numero (elevato) di compiti (in questo caso, chiamando un programma shell):

import Queue
import threading
import multiprocessing
import subprocess

q = Queue.Queue()
for i in range(30): # Put 30 tasks in the queue
    q.put(i)

def worker():
    while True:
        item = q.get()
        # Execute a task: call a shell program and wait until it completes
        subprocess.call("echo " + str(item), shell=True)
        q.task_done()

cpus = multiprocessing.cpu_count() # Detect number of cores
print("Creating %d threads" % cpus)
for i in range(cpus):
     t = threading.Thread(target=worker)
     t.daemon = True
     t.start()

q.join() # Block until all tasks are done

@shavenwarthog sicuramente si può regolare la variabile "cpus" in base alle proprie esigenze. Ad ogni modo, la chiamata al sottoprocesso genererà sottoprocessi e questi verranno allocati cpus dal sistema operativo (il "processo padre" di python non significa "stessa CPU" per i sottoprocessi).
delfino

2
hai ragione, il mio commento su "i thread sono avviati sulla stessa CPU del processo genitore" è sbagliato. Grazie per la risposta!
johntellsall,

1
forse vale la pena notare che, diversamente dal multithreading che utilizza lo stesso spazio di memoria, il multiprocessing non può condividere variabili / dati con la stessa facilità. +1 però.
fantabolous,

22

Python 3 ha la possibilità di avviare attività parallele . Questo semplifica il nostro lavoro.

Dispone di pool di thread e pool di processi .

Di seguito viene fornita una panoramica:

Esempio ThreadPoolExecutor ( sorgente )

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

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

18

Utilizzando il nuovo ardente modulo concurrent.futures

def sqr(val):
    import time
    time.sleep(0.1)
    return val * val

def process_result(result):
    print(result)

def process_these_asap(tasks):
    import concurrent.futures

    with concurrent.futures.ProcessPoolExecutor() as executor:
        futures = []
        for task in tasks:
            futures.append(executor.submit(sqr, task))

        for future in concurrent.futures.as_completed(futures):
            process_result(future.result())
        # Or instead of all this just do:
        # results = executor.map(sqr, tasks)
        # list(map(process_result, results))

def main():
    tasks = list(range(10))
    print('Processing {} tasks'.format(len(tasks)))
    process_these_asap(tasks)
    print('Done')
    return 0

if __name__ == '__main__':
    import sys
    sys.exit(main())

L'approccio degli esecutori potrebbe sembrare familiare a tutti coloro che prima si sono sporcati le mani con Java.

Anche su una nota a margine: per mantenere l'universo sano, non dimenticare di chiudere i tuoi pool / esecutori se non usi il withcontesto (che è così fantastico che lo fa per te)


17

Per me, l'esempio perfetto per il threading è il monitoraggio di eventi asincroni. Guarda questo codice.

# thread_test.py
import threading
import time

class Monitor(threading.Thread):
    def __init__(self, mon):
        threading.Thread.__init__(self)
        self.mon = mon

    def run(self):
        while True:
            if self.mon[0] == 2:
                print "Mon = 2"
                self.mon[0] = 3;

Puoi giocare con questo codice aprendo una sessione IPython e facendo qualcosa del tipo:

>>> from thread_test import Monitor
>>> a = [0]
>>> mon = Monitor(a)
>>> mon.start()
>>> a[0] = 2
Mon = 2
>>>a[0] = 2
Mon = 2

Aspetta qualche minuto

>>> a[0] = 2
Mon = 2

1
AttributeError: l'oggetto 'Monitor' non ha attributi 'stop'?
Pandita,

5
Non stai facendo saltare i cicli della CPU mentre aspetti che si verifichi il tuo evento? Non sempre una cosa molto pratica da fare.
magnate

3
Come dice il magnate, questo sarà costantemente in esecuzione. Come minimo potresti aggiungere un breve sonno, ad esempio sleep (0.1), che probabilmente ridurrebbe significativamente l'utilizzo della cpu su un semplice esempio come questo.
fantabolous,

3
Questo è un esempio orribile, che spreca un nucleo. Aggiungi almeno un sonno, ma la soluzione corretta è usare un meccanismo di segnalazione.
PureW,

16

La maggior parte della documentazione e dei tutorial usa Python ThreadingeQueue modulo e potrebbero sembrare travolgenti per i principianti.

Forse considera il concurrent.futures.ThreadPoolExecutor modulo di Python 3.

Combinato con la withclausola e la comprensione dell'elenco potrebbe essere un vero fascino.

from concurrent.futures import ThreadPoolExecutor, as_completed

def get_url(url):
    # Your actual program here. Using threading.Lock() if necessary
    return ""

# List of URLs to fetch
urls = ["url1", "url2"]

with ThreadPoolExecutor(max_workers = 5) as executor:

    # Create threads
    futures = {executor.submit(get_url, url) for url in urls}

    # as_completed() gives you the threads once finished
    for f in as_completed(futures):
        # Get the results
        rs = f.result()

15

Ho visto molti esempi qui in cui non veniva eseguito alcun lavoro reale, ed erano per lo più legati alla CPU. Ecco un esempio di un'attività associata alla CPU che calcola tutti i numeri primi tra 10 milioni e 10,05 milioni. Ho usato tutti e quattro i metodi qui:

import math
import timeit
import threading
import multiprocessing
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def time_stuff(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{} seconds".format(t1 - t0))
    return wrapper

def find_primes_in(nmin, nmax):
    """
    Compute a list of prime numbers between the given minimum and maximum arguments
    """
    primes = []

    # Loop from minimum to maximum
    for current in range(nmin, nmax + 1):

        # Take the square root of the current number
        sqrt_n = int(math.sqrt(current))
        found = False

        # Check if the any number from 2 to the square root + 1 divides the current numnber under consideration
        for number in range(2, sqrt_n + 1):

            # If divisible we have found a factor, hence this is not a prime number, lets move to the next one
            if current % number == 0:
                found = True
                break

        # If not divisible, add this number to the list of primes that we have found so far
        if not found:
            primes.append(current)

    # I am merely printing the length of the array containing all the primes, but feel free to do what you want
    print(len(primes))

@time_stuff
def sequential_prime_finder(nmin, nmax):
    """
    Use the main process and main thread to compute everything in this case
    """
    find_primes_in(nmin, nmax)

@time_stuff
def threading_prime_finder(nmin, nmax):
    """
    If the minimum is 1000 and the maximum is 2000 and we have four workers,
    1000 - 1250 to worker 1
    1250 - 1500 to worker 2
    1500 - 1750 to worker 3
    1750 - 2000 to worker 4
    so let’s split the minimum and maximum values according to the number of workers
    """
    nrange = nmax - nmin
    threads = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)

        # Start the thread with the minimum and maximum split up to compute
        # Parallel computation will not work here due to the GIL since this is a CPU-bound task
        t = threading.Thread(target = find_primes_in, args = (start, end))
        threads.append(t)
        t.start()

    # Don’t forget to wait for the threads to finish
    for t in threads:
        t.join()

@time_stuff
def processing_prime_finder(nmin, nmax):
    """
    Split the minimum, maximum interval similar to the threading method above, but use processes this time
    """
    nrange = nmax - nmin
    processes = []
    for i in range(8):
        start = int(nmin + i * nrange/8)
        end = int(nmin + (i + 1) * nrange/8)
        p = multiprocessing.Process(target = find_primes_in, args = (start, end))
        processes.append(p)
        p.start()

    for p in processes:
        p.join()

@time_stuff
def thread_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use a thread pool executor this time.
    This method is slightly faster than using pure threading as the pools manage threads more efficiently.
    This method is still slow due to the GIL limitations since we are doing a CPU-bound task.
    """
    nrange = nmax - nmin
    with ThreadPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

@time_stuff
def process_executor_prime_finder(nmin, nmax):
    """
    Split the min max interval similar to the threading method, but use the process pool executor.
    This is the fastest method recorded so far as it manages process efficiently + overcomes GIL limitations.
    RECOMMENDED METHOD FOR CPU-BOUND TASKS
    """
    nrange = nmax - nmin
    with ProcessPoolExecutor(max_workers = 8) as e:
        for i in range(8):
            start = int(nmin + i * nrange/8)
            end = int(nmin + (i + 1) * nrange/8)
            e.submit(find_primes_in, start, end)

def main():
    nmin = int(1e7)
    nmax = int(1.05e7)
    print("Sequential Prime Finder Starting")
    sequential_prime_finder(nmin, nmax)
    print("Threading Prime Finder Starting")
    threading_prime_finder(nmin, nmax)
    print("Processing Prime Finder Starting")
    processing_prime_finder(nmin, nmax)
    print("Thread Executor Prime Finder Starting")
    thread_executor_prime_finder(nmin, nmax)
    print("Process Executor Finder Starting")
    process_executor_prime_finder(nmin, nmax)

main()

Ecco i risultati sul mio computer a quattro core Mac OS X.

Sequential Prime Finder Starting
9.708213827005238 seconds
Threading Prime Finder Starting
9.81836523200036 seconds
Processing Prime Finder Starting
3.2467174359990167 seconds
Thread Executor Prime Finder Starting
10.228896902000997 seconds
Process Executor Finder Starting
2.656402041000547 seconds

1
@TheUnfunCat nessun esecutore di processo è molto meglio del threading per attività legate alla cpu
PirateApp

1
Ottima risposta amico. Posso confermare che in Python 3.6 su Windows (almeno) ThreadPoolExecutor non fa nulla di buono per le attività pesanti della CPU. Non utilizza i core per il calcolo. Mentre ProcessPoolExecutor copia i dati in OGNI processo che genera, è mortale per matrici di grandi dimensioni.
Anatoly Alekseev il

1
Esempio molto utile, ma non capisco come abbia mai funzionato. Abbiamo bisogno di una if __name__ == '__main__':prima della chiamata principale, altrimenti la misura stessa e stampe spawn Un tentativo è stato fatto per avviare un nuovo processo prima ... .
Stein,

1
@Stein Credo che sia solo un problema su Windows.
AMC

12

Ecco l'esempio molto semplice dell'importazione CSV usando il threading. (L'inclusione della biblioteca può differire per scopi diversi.)

Funzioni di supporto:

from threading import Thread
from project import app
import csv


def import_handler(csv_file_name):
    thr = Thread(target=dump_async_csv_data, args=[csv_file_name])
    thr.start()

def dump_async_csv_data(csv_file_name):
    with app.app_context():
        with open(csv_file_name) as File:
            reader = csv.DictReader(File)
            for row in reader:
                # DB operation/query

Funzione conducente:

import_handler(csv_file_name)

9

Vorrei contribuire con un semplice esempio e le spiegazioni che ho trovato utili quando ho dovuto affrontare questo problema da solo.

In questa risposta troverai alcune informazioni su GIL (blocco dell'interprete globale) di Python e un semplice esempio quotidiano scritto usando multiprocessing.dummy oltre ad alcuni semplici benchmark.

Global Interpreter Lock (GIL)

Python non consente il multi-threading nel vero senso della parola. Ha un pacchetto multi-thread, ma se vuoi multi-thread per velocizzare il tuo codice, di solito non è una buona idea usarlo.

Python ha un costrutto chiamato global interpreter lock (GIL). GIL si assicura che solo uno dei tuoi "thread" possa essere eseguito contemporaneamente. Un thread acquisisce il GIL, fa un po 'di lavoro, quindi passa il GIL al thread successivo.

Questo accade molto rapidamente, quindi all'occhio umano può sembrare che i tuoi thread si stiano eseguendo in parallelo, ma in realtà stanno facendo a turno usando lo stesso core della CPU.

Tutto questo passaggio GIL aggiunge sovraccarico all'esecuzione. Ciò significa che se si desidera rendere più veloce l'esecuzione del codice, l'utilizzo del pacchetto di threading spesso non è una buona idea.

Ci sono ragioni per usare il pacchetto di threading di Python. Se vuoi eseguire alcune cose contemporaneamente e l'efficienza non è un problema, allora è totalmente a posto e conveniente. O se stai eseguendo un codice che deve attendere qualcosa (come alcuni I / O), potrebbe avere molto senso. Ma la libreria di threading non ti permetterà di usare core di CPU extra.

Il multi-threading può essere esternalizzato al sistema operativo (eseguendo l'elaborazione multipla) e alcune applicazioni esterne che chiamano il tuo codice Python (ad esempio Spark o Hadoop ) o un codice che chiama il tuo codice Python (ad esempio: potresti chiedi al tuo codice Python di chiamare una funzione C che fa le costose cose multi-thread).

Perché questo conta

Perché molte persone passano molto tempo a cercare colli di bottiglia nel loro fantastico codice multi-thread di Python prima di scoprire cos'è il GIL.

Una volta che queste informazioni sono chiare, ecco il mio codice:

#!/bin/python
from multiprocessing.dummy import Pool
from subprocess import PIPE,Popen
import time
import os

# In the variable pool_size we define the "parallelness".
# For CPU-bound tasks, it doesn't make sense to create more Pool processes
# than you have cores to run them on.
#
# On the other hand, if you are using I/O-bound tasks, it may make sense
# to create a quite a few more Pool processes than cores, since the processes
# will probably spend most their time blocked (waiting for I/O to complete).
pool_size = 8

def do_ping(ip):
    if os.name == 'nt':
        print ("Using Windows Ping to " + ip)
        proc = Popen(['ping', ip], stdout=PIPE)
        return proc.communicate()[0]
    else:
        print ("Using Linux / Unix Ping to " + ip)
        proc = Popen(['ping', ip, '-c', '4'], stdout=PIPE)
        return proc.communicate()[0]


os.system('cls' if os.name=='nt' else 'clear')
print ("Running using threads\n")
start_time = time.time()
pool = Pool(pool_size)
website_names = ["www.google.com","www.facebook.com","www.pinterest.com","www.microsoft.com"]
result = {}
for website_name in website_names:
    result[website_name] = pool.apply_async(do_ping, args=(website_name,))
pool.close()
pool.join()
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Now we do the same without threading, just to compare time
print ("\nRunning NOT using threads\n")
start_time = time.time()
for website_name in website_names:
    do_ping(website_name)
print ("\n--- Execution took {} seconds ---".format((time.time() - start_time)))

# Here's one way to print the final output from the threads
output = {}
for key, value in result.items():
    output[key] = value.get()
print ("\nOutput aggregated in a Dictionary:")
print (output)
print ("\n")

print ("\nPretty printed output: ")
for key, value in output.items():
    print (key + "\n")
    print (value)

7

Ecco il multi threading con un semplice esempio che sarà utile. Puoi eseguirlo e capire facilmente come funziona il multi-threading in Python. Ho usato un lucchetto per impedire l'accesso ad altri thread fino a quando i thread precedenti non hanno completato il loro lavoro. Con l'uso di questa riga di codice,

tLock = threading.BoundedSemaphore (valore = 4)

puoi consentire un numero di processi alla volta e mantenere il resto dei thread che verranno eseguiti in seguito o dopo aver terminato i processi precedenti.

import threading
import time

#tLock = threading.Lock()
tLock = threading.BoundedSemaphore(value=4)
def timer(name, delay, repeat):
    print  "\r\nTimer: ", name, " Started"
    tLock.acquire()
    print "\r\n", name, " has the acquired the lock"
    while repeat > 0:
        time.sleep(delay)
        print "\r\n", name, ": ", str(time.ctime(time.time()))
        repeat -= 1

    print "\r\n", name, " is releaseing the lock"
    tLock.release()
    print "\r\nTimer: ", name, " Completed"

def Main():
    t1 = threading.Thread(target=timer, args=("Timer1", 2, 5))
    t2 = threading.Thread(target=timer, args=("Timer2", 3, 5))
    t3 = threading.Thread(target=timer, args=("Timer3", 4, 5))
    t4 = threading.Thread(target=timer, args=("Timer4", 5, 5))
    t5 = threading.Thread(target=timer, args=("Timer5", 0.1, 5))

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()

    print "\r\nMain Complete"

if __name__ == "__main__":
    Main()

5

Con il prestito da questo post sappiamo di scegliere tra multithreading, multiprocessing e async / asyncioe il loro utilizzo.

Python 3 ha una nuova libreria integrata per la concorrenza e il parallelismo: concurrent.futures

Quindi, attraverso un esperimento, dimostrerò di eseguire quattro attività (cioè il .sleep()metodo) nel Threading-Poolmodo seguente:

from concurrent.futures import ThreadPoolExecutor, as_completed
from time import sleep, time

def concurrent(max_worker=1):
    futures = []

    tick = time()
    with ThreadPoolExecutor(max_workers=max_worker) as executor:
        futures.append(executor.submit(sleep, 2))  # Two seconds sleep
        futures.append(executor.submit(sleep, 1))
        futures.append(executor.submit(sleep, 7))
        futures.append(executor.submit(sleep, 3))

        for future in as_completed(futures):
            if future.result() is not None:
                print(future.result())

    print('Total elapsed time by {} workers:'.format(max_worker), time()-tick)

concurrent(5)
concurrent(4)
concurrent(3)
concurrent(2)
concurrent(1)

Produzione:

Total elapsed time by 5 workers: 7.007831811904907
Total elapsed time by 4 workers: 7.007944107055664
Total elapsed time by 3 workers: 7.003149509429932
Total elapsed time by 2 workers: 8.004627466201782
Total elapsed time by 1 workers: 13.013478994369507

[ NOTA ]:

  • Come puoi vedere nei risultati precedenti, il caso migliore è stato di 3 lavoratori per queste quattro attività.
  • Se si dispone di un'attività di processo anziché I / O associato o il blocco ( multiprocessingvs threading) è possibile modificare ThreadPoolExecutorin ProcessPoolExecutor.

4

Nessuna delle soluzioni precedenti utilizzava più core sul mio server GNU / Linux (dove non ho i diritti di amministratore). Funzionavano su un solo core.

Ho usato l' os.forkinterfaccia di livello inferiore per generare più processi. Questo è il codice che ha funzionato per me:

from os import fork

values = ['different', 'values', 'for', 'threads']

for i in range(len(values)):
    p = fork()
    if p == 0:
        my_function(values[i])
        break

2
import threading
import requests

def send():

  r = requests.get('https://www.stackoverlow.com')

thread = []
t = threading.Thread(target=send())
thread.append(t)
t.start()

1
@sP_ Immagino perché poi hai oggetti thread in modo da poter aspettare che finiscano.
Aleksandar Makragić,

1
t = threading.Thread (target = send ()) dovrebbe essere t = threading.Thread (target = send)
TRiNE

Sto annullando questa risposta perché non fornisce una spiegazione di come migliora le risposte esistenti, oltre a contenere una grave inesattezza.
Jules il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.