web-dev-qa-db-fra.com

Greenlet Vs. Les fils

Je suis novice dans les gevents et les greenlets. J'ai trouvé une bonne documentation sur la façon de travailler avec eux, mais personne ne m'a expliqué comment et quand utiliser des greenlets!

  • En quoi sont-ils vraiment bons?
  • Est-ce une bonne idée de les utiliser sur un serveur proxy ou non?
  • Pourquoi pas les fils?

Ce dont je ne suis pas sûr, c'est comment ils peuvent nous fournir une concurrence si ce sont essentiellement des co-routines.

128
Rsh

Les Greenlets fournissent des accès concurrents mais pas parallélisme. La simultanéité est lorsque le code peut s'exécuter indépendamment d'un autre code. Le parallélisme est l'exécution simultanée d'un code concurrent. Le parallélisme est particulièrement utile lorsqu'il y a beaucoup de travail à faire dans l'espace utilisateur, et que cela nécessite généralement beaucoup de ressources processeur. La simultanéité est utile pour résoudre les problèmes, permettant ainsi de planifier et de gérer plus facilement différentes parties en parallèle.

Les Greenlets brillent vraiment dans la programmation réseau où les interactions avec un socket peuvent se produire indépendamment des interactions avec les autres sockets. Ceci est un exemple classique de concurrence. Étant donné que chaque greenlet s'exécute dans son propre contexte, vous pouvez continuer à utiliser des API synchrones sans thread. C'est une bonne chose, car les threads sont très coûteux en termes de mémoire virtuelle et de surcharge du noyau. La simultanéité que vous pouvez obtenir avec les threads est donc nettement inférieure. En outre, le threading dans Python est plus coûteux et plus limité que d’habitude en raison de la GIL. Les alternatives à la simultanéité sont généralement des projets tels que Twisted, libevent, libuv, node.js, etc., où tous vos partages de code le même contexte d'exécution et enregistrez les gestionnaires d'événements.

C'est une excellente idée d'utiliser des greenlets (avec un support réseau approprié tel que gevent) pour écrire un proxy, car votre traitement des demandes peut s'exécuter indépendamment et doit être écrit en tant que tel.

Les Greenlets fournissent une concurrence pour les raisons que j'ai données précédemment. La concurrence n'est pas le parallélisme. En masquant l’enregistrement des événements et en effectuant pour vous la planification d’appels bloquant normalement le thread actuel, des projets tels que gevent exposent cette simultanéité sans nécessiter le changement d’API asynchrone et à un coût considérablement inférieur pour votre système.

182
Matt Joiner

En prenant la réponse de @ Max et en y ajoutant un peu de pertinence pour la mise à l'échelle, vous pouvez voir la différence. J'ai atteint cet objectif en modifiant les URL à renseigner comme suit:

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)

J'ai dû abandonner la version multiprocessus car elle est tombée avant que j'en ai 500; mais à 10 000 itérations:

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

Donc, vous pouvez voir qu'il y a une différence significative dans les E/S utilisant gevent

18
TemporalBeing

C'est assez intéressant à analyser. Voici un code permettant de comparer les performances des greenlets par rapport au pool de multitraitement par rapport au 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 __== "__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()

voici les résultats:

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

Je pense que Greenlet affirme qu’elle n’est pas liée par GIL contrairement à la bibliothèque multithreading. En outre, la documentation Greenlet indique qu’il est destiné aux opérations réseau. Pour un fonctionnement intensif en réseau, la commutation de threads convient et vous pouvez constater que l'approche multithreading est assez rapide. De plus, il est toujours préférable d’utiliser les bibliothèques officielles de Python; J'ai essayé d'installer greenlet sur Windows et j'ai rencontré un problème de dépendance de la DLL. J'ai donc lancé ce test sur un Linux Linux. Essayez toujours d’écrire un code en espérant qu’il fonctionne sur n’importe quel ordinateur.

8
max

En corrigeant la réponse de @TemporalBeing ci-dessus, les greenlets ne sont pas "plus rapides" que les threads et c’est une technique de programmation incorrecte pour générer 60000 threads afin de résoudre un problème de simultanéité. problème, un petit groupe de threads est plutôt approprié. Voici une comparaison plus raisonnable (de mon post reddit en réponse aux personnes citant ceci SO post).

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)

Voici quelques résultats:

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

le malentendu que tout le monde a à propos du non-blocage IO avec Python est la conviction que l’interprète Python peut assister au travail) de récupérer des résultats de sockets à grande échelle plus rapidement que les connexions réseau elles-mêmes peuvent renvoyer des E/S. Cela est certainement vrai dans certains cas, mais presque aussi souvent que les gens le pensent, car le Python = L’interprète est vraiment très lent. Dans mon article de blog ici , j’illustre des profils graphiques qui montrent que même pour des choses très simples, si vous avez un accès réseau rapide et précis à des bases de données ou Serveurs DNS, ces services peuvent revenir beaucoup plus rapidement que le code Python) peut gérer plusieurs milliers de ces connexions.

5
zzzeek