Correggendo la risposta di @TemporalBeing sopra, i greenlet non sono "più veloci" dei thread ed è una tecnica di programmazione errata generare spawn di 60000 thread per risolvere un problema di concorrenza, è invece appropriato un piccolo pool di thread. Ecco un confronto più ragionevole (dal mio post reddit in risposta alle persone che citano questo post SO).
import gevent
from gevent import socket as gsock
import socket as sock
import threading
from datetime import datetime
def timeit(fn, URLS):
t1 = datetime.now()
fn()
t2 = datetime.now()
print(
"%s / %d hostnames, %s seconds" % (
fn.__name__,
len(URLS),
(t2 - t1).total_seconds()
)
)
def run_gevent_without_a_timeout():
ip_numbers = []
def greenlet(domain_name):
ip_numbers.append(gsock.gethostbyname(domain_name))
jobs = [gevent.spawn(greenlet, domain_name) for domain_name in URLS]
gevent.joinall(jobs)
assert len(ip_numbers) == len(URLS)
def run_threads_correctly():
ip_numbers = []
def process():
while queue:
try:
domain_name = queue.pop()
except IndexError:
pass
else:
ip_numbers.append(sock.gethostbyname(domain_name))
threads = [threading.Thread(target=process) for i in range(50)]
queue = list(URLS)
for t in threads:
t.start()
for t in threads:
t.join()
assert len(ip_numbers) == len(URLS)
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org',
'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
for NUM in (5, 50, 500, 5000, 10000):
URLS = []
for _ in range(NUM):
for url in URLS_base:
URLS.append(url)
print("--------------------")
timeit(run_gevent_without_a_timeout, URLS)
timeit(run_threads_correctly, URLS)
Ecco alcuni risultati:
--------------------
run_gevent_without_a_timeout / 30 hostnames, 0.044888 seconds
run_threads_correctly / 30 hostnames, 0.019389 seconds
--------------------
run_gevent_without_a_timeout / 300 hostnames, 0.186045 seconds
run_threads_correctly / 300 hostnames, 0.153808 seconds
--------------------
run_gevent_without_a_timeout / 3000 hostnames, 1.834089 seconds
run_threads_correctly / 3000 hostnames, 1.569523 seconds
--------------------
run_gevent_without_a_timeout / 30000 hostnames, 19.030259 seconds
run_threads_correctly / 30000 hostnames, 15.163603 seconds
--------------------
run_gevent_without_a_timeout / 60000 hostnames, 35.770358 seconds
run_threads_correctly / 60000 hostnames, 29.864083 seconds
il malinteso che tutti hanno sull'IO non bloccante con Python è la convinzione che l'interprete Python possa occuparsi del lavoro di recupero dei risultati dai socket su larga scala più velocemente di quanto le connessioni di rete stesse possano restituire IO. Anche se questo è certamente vero in alcuni casi, non lo è quasi tutte le volte che la gente pensa, perché l'interprete Python è molto, molto lento. Nel mio post di blog qui , illustrerò alcuni profili grafici che mostrano che per cose anche molto semplici, se hai a che fare con un accesso alla rete rapido e veloce a cose come database o server DNS, quei servizi possono tornare molto più velocemente del codice Python può occuparsi di molte migliaia di tali connessioni.