2013-03-21 11 views
91

Sono nuovo di gevents e greenlets. Ho trovato una buona documentazione su come lavorare con loro, ma nessuno mi ha dato una giustificazione su come e quando dovrei usare i greenlets!Greenlet vs. Thread

  • In cosa sono veramente bravi?
  • È consigliabile utilizzarli in un server proxy oppure no?
  • Perché non thread?

Quello di cui non sono sicuro è come possano fornirci la concorrenza se sono fondamentalmente co-routines.

+1

@Imran Si tratta di greenthreads in Java. La mia domanda riguarda Greenlet in Python. Mi sto perdendo qualcosa ? – Rsh

+0

Afaik, i thread in python non sono realmente concomitanti a causa del blocco dell'interprete globale. Quindi si ridurrebbe a confrontare il sovraccarico di entrambe le soluzioni. Anche se capisco che ci sono diverse implementazioni di Python, quindi questo potrebbe non valere per tutti loro. – didierc

+3

@didierc CPython (e PyPy al momento) non interpreteranno il codice Python (byte) * in parallelo * (cioè, fisicamente fisicamente nello stesso momento su due core CPU distinti). Tuttavia, non tutto ciò che fa un programma Python è sotto GIL (esempi comuni sono syscalls incluse le funzioni I/O e C che rilasciano deliberatamente il GIL), e 'threading.Thread' è in realtà un thread del sistema operativo con tutte le ramificazioni. Quindi non è proprio così semplice. A proposito, Jython non ha GIL AFAIK e PyPy sta cercando di liberarsene. – delnan

risposta

142

Greenlets forniscono simultanea ma non parallelismo. La concorrenza è quando il codice può essere eseguito indipendentemente da un altro codice. Parallelismo è l'esecuzione simultanea di codice simultaneo. Il parallelismo è particolarmente utile quando c'è molto lavoro da fare nello userspace, e di solito è roba da CPU. La concorrenza è utile per rompere i problemi, consentendo di pianificare e gestire più parti in parallelo più facilmente.

I greenlet brillano davvero nella programmazione di rete in cui le interazioni con un socket possono verificarsi indipendentemente dalle interazioni con altri socket. Questo è un classico esempio di concorrenza. Poiché ogni greenlet viene eseguita nel proprio contesto, è possibile continuare a utilizzare le API sincrone senza eseguire il threading. Ciò è positivo perché i thread sono molto costosi in termini di memoria virtuale e overhead del kernel, quindi la concorrenza che è possibile ottenere con i thread è significativamente inferiore. Inoltre, il threading in Python è più costoso e più limitato del solito a causa della GIL. Le alternative alla concorrenza sono in genere progetti come Twisted, libevent, libuv, node.js ecc., In cui tutto il codice condivide lo stesso contesto di esecuzione e registra i gestori di eventi.

È un'ottima idea utilizzare i greenlet (con supporto di rete appropriato come tramite gevent) per scrivere un proxy, poiché la gestione delle richieste è in grado di eseguirsi in modo indipendente e deve essere scritta come tale.

I greenlet forniscono la concorrenza per i motivi che ho dato in precedenza. La concorrenza non è il parallelismo. Nascondendo la registrazione degli eventi e eseguendo la pianificazione per le chiamate che normalmente bloccheranno il thread corrente, i progetti come gevent espongono questa concorrenza senza richiedere modifiche a un'API asincrona e a costi significativamente inferiori per il sistema.

+1

Grazie, solo due piccole domande: 1) È possibile combinare questa soluzione con il multiprocessing per ottenere un throughput più elevato? 2) Non so ancora perché usare mai i fili? Possiamo considerarli un'implementazione ingenua e basilare della concorrenza nella libreria standard python? – Rsh

+6

1) Sì, assolutamente. Non dovresti farlo prematuramente, ma a causa di una serie di fattori che vanno oltre lo scopo di questa domanda, avere più richieste di elaborazione dei processi ti darà un throughput più elevato. 2) I thread del sistema operativo sono pianificati in modo preventivo e completamente parallelizzati per impostazione predefinita. Sono di default in Python perché Python espone l'interfaccia di threading nativa ei thread sono il miglior comune e il minimo comune denominatore per il parallelismo e la concorrenza nei sistemi operativi moderni. –

+6

Dovrei menzionare che non dovresti nemmeno usare i greenlet finché i thread non sono soddisfacenti (di solito questo si verifica a causa del numero di connessioni simultanee che stai gestendo, e il conteggio dei thread o GIL ti sta dando fastidio), e anche allora solo se non ci sono altre opzioni disponibili. La libreria standard di Python e la maggior parte delle librerie di terze parti * si aspettano che la concorrenza sia raggiunta attraverso i thread, quindi si può ottenere un comportamento strano se lo si fornisce tramite greenlet. –

10

Questo è abbastanza interessante da analizzare. Ecco un codice per confrontare le prestazioni di greenlets rispetto multiprocessing piscina contro il multi-threading:

import gevent 
from gevent import socket as gsock 
import socket as sock 
from multiprocessing import Pool 
from threading import Thread 
from datetime import datetime 

class IpGetter(Thread): 
    def __init__(self, domain): 
     Thread.__init__(self) 
     self.domain = domain 
    def run(self): 
     self.ip = sock.gethostbyname(self.domain) 

if __name__ == "__main__": 
    URLS = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org'] 
    t1 = datetime.now() 
    jobs = [gevent.spawn(gsock.gethostbyname, url) for url in URLS] 
    gevent.joinall(jobs, timeout=2) 
    t2 = datetime.now() 
    print "Using gevent it took: %s" % (t2-t1).total_seconds() 
    print "-----------" 
    t1 = datetime.now() 
    pool = Pool(len(URLS)) 
    results = pool.map(sock.gethostbyname, URLS) 
    t2 = datetime.now() 
    pool.close() 
    print "Using multiprocessing it took: %s" % (t2-t1).total_seconds() 
    print "-----------" 
    t1 = datetime.now() 
    threads = [] 
    for url in URLS: 
     t = IpGetter(url) 
     t.start() 
     threads.append(t) 
    for t in threads: 
     t.join() 
    t2 = datetime.now() 
    print "Using multi-threading it took: %s" % (t2-t1).total_seconds() 

ecco i risultati:

Using gevent it took: 0.083758 
----------- 
Using multiprocessing it took: 0.023633 
----------- 
Using multi-threading it took: 0.008327 

Credo che greenlet sostiene che non è vincolato da GIL a differenza la libreria multithreading. Inoltre, il dott. Greenlet dice che è pensato per le operazioni di rete. Per un'operazione intensiva di rete, il cambio di thread va bene e puoi vedere che l'approccio al multithreading è piuttosto veloce. Inoltre è sempre preferibile usare le librerie ufficiali di python; Ho provato a installare greenlet su Windows e ho riscontrato un problema di dipendenza da dll, quindi ho eseguito questo test su un Linux vm. Provare sempre a scrivere un codice con la speranza che funzioni su qualsiasi macchina.

+17

Nota che '' getsockbyname'' memorizza nella cache i risultati a livello di SO (almeno sulla mia macchina). Quando viene richiamato su un DNS precedentemente sconosciuto o scaduto, esegue effettivamente una query di rete, che potrebbe richiedere del tempo. Quando viene invocato su un nome host che è stato risolto di recente, restituirà la risposta molto più velocemente. Di conseguenza, la tua metodologia di misurazione è imperfetta qui. Questo spiega i tuoi strani risultati: gevent non può essere molto peggio del multithreading - entrambi non sono realmente paralleli a livello di VM. –

+0

@ KT. questo è un punto eccellente. Avresti bisogno di eseguire quel test molte volte e prendere mezzi, modalità e mediane per ottenere una buona immagine. Si noti inoltre che i router memorizzano nella cache i percorsi di instradamento per i protocolli e dove non memorizzano nella cache i percorsi di instradamento che è possibile ottenere un ritardo diverso dal traffico del percorso di instradamento DNS diverso. E i server di DNS pesantemente cache. Potrebbe essere preferibile misurare il threading utilizzando time.clock() dove vengono utilizzati i cicli cpu invece di essere influenzati dalla latenza sull'hardware di rete. Ciò potrebbe eliminare altri servizi OS che si intrufolano e aggiungono tempo dalle misurazioni. – DevPlayer

+0

Oh ed è possibile eseguire un flush DNS a livello di SO tra questi tre test, ma ancora una volta questo ridurrebbe solo i dati falsi dalla cache DNS locale. – DevPlayer

18

Prendendo la risposta di @ Max e aggiungendo qualche rilevanza per il ridimensionamento, è possibile vedere la differenza. Ho raggiunto questo cambiando gli URL da riempire come segue:

URLS_base = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org'] 
URLS = [] 
for _ in range(10000): 
    for url in URLS_base: 
     URLS.append(url) 

ho dovuto abbandonare la versione multiprocesso che cadeva prima avevo 500; ma a 10.000 iterazioni:

Using gevent it took: 3.756914 
----------- 
Using multi-threading it took: 15.797028 

Così si può vedere c'è qualche differenza significativa nel I/O utilizzando gevent