Este grupo de especialistas luego de algunos días de investigación y recopilar de datos, obtuvieron los datos necesarios, queremos que resulte útil para ti en tu plan.
Solución:
Los greenlets proporcionan concurrencia pero no paralelismo. La concurrencia es cuando el código puede ejecutarse independientemente de otro código. El paralelismo es la ejecución de código concurrente simultáneamente. El paralelismo es particularmente útil cuando hay mucho trabajo por hacer en el espacio de usuario, y eso suele ser algo pesado para la CPU. La simultaneidad es útil para desglosar problemas, lo que permite que diferentes partes se programen y administren más fácilmente en paralelo.
Los greenlets realmente brillan en la programación de redes donde las interacciones con un socket pueden ocurrir independientemente de las interacciones con otros sockets. Este es un ejemplo clásico de concurrencia. Debido a que cada greenlet se ejecuta en su propio contexto, puede continuar usando las API síncronas sin subprocesos. Esto es bueno porque los subprocesos son muy costosos en términos de memoria virtual y sobrecarga del kernel, por lo que la concurrencia que puede lograr con los subprocesos es significativamente menor. Además, la creación de subprocesos en Python es más costosa y más limitada de lo habitual debido a la GIL. Las alternativas a la concurrencia suelen ser proyectos como Twisted, libevent, libuv, node.js, etc., donde todo su código comparte el mismo contexto de ejecución y registra controladores de eventos.
Es una excelente idea usar greenlets (con el soporte de red adecuado, como a través de gevent) para escribir un proxy, ya que su manejo de solicitudes puede ejecutarse de forma independiente y debe escribirse como tal.
Los greenlets proporcionan concurrencia por las razones que mencioné anteriormente. Concurrencia no es paralelismo. Al ocultar el registro de eventos y realizar la programación por usted en llamadas que normalmente bloquearían el hilo actual, los proyectos como gevent exponen esta concurrencia sin necesidad de cambiar a una API asíncrona y a un costo significativamente menor para su sistema.
- Concurrencia no es paralelismo
- Hilos frente a procesos
- Multiprocesamiento frente a subprocesos
- GIL frente a CPython
Tomando la respuesta de @ Max y agregando algo de relevancia para escalar, puede ver la diferencia. Logré esto cambiando las URL para que se llenen de la siguiente manera:
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)
Tuve que abandonar la versión multiproceso ya que cayó antes de tener 500; pero a las 10.000 iteraciones:
Using gevent it took: 3.756914
-----------
Using multi-threading it took: 15.797028
Entonces puede ver que hay una diferencia significativa en la E/S usando gevent
Al corregir la respuesta anterior de @TemporalBeing, los greenlets no son “más rápidos” que los hilos y es una técnica de programación incorrecta para generar 60000 hilos para resolver un problema de simultaneidad, un pequeño conjunto de subprocesos es apropiado. Aquí hay una comparación más razonable (de mi publicación de reddit en respuesta a las personas que citan esta publicación 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)
Aquí hay algunos resultados:
--------------------
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
el malentendido que todos tienen acerca de no bloquear IO con Python es la creencia de que el intérprete de Python puede ocuparse del trabajo de recuperar resultados de sockets a gran escala más rápido de lo que las conexiones de red pueden devolver IO. Si bien esto es ciertamente true en algunos casos, no es true casi tan a menudo como la gente piensa, porque el intérprete de Python es muy, muy lento. En mi publicación de blog aquí, ilustro algunos perfiles gráficos que muestran que incluso para cosas muy simples, si se trata de un acceso de red nítido y rápido a cosas como bases de datos o servidores DNS, esos servicios pueden volver mucho más rápido que el código de Python. puede atender a muchos miles de esas conexiones.
valoraciones y reseñas
Si eres capaz, eres capaz de dejar una noticia acerca de qué le añadirías a esta división.