Greenlet Vs. Threads

Jestem nowy dla gevents i greenlets. Znalazłem dobrą dokumentację, jak z nimi pracować, ale żaden nie dał mi uzasadnienia, jak i kiedy powinienem używać greenlets!

    W czym są naprawdę dobrzy? Czy używanie ich na serwerze proxy jest dobrym pomysłem, czy nie?
  • Dlaczego nie wątki?

Nie jestem pewien, w jaki sposób mogą zapewnić nam współbieżność, jeśli są w zasadzie współbieżne.

Author: Robert Siemer, 2013-03-21

4 answers

Greenlets zapewniają współbieżność, ale nie równoległość. Współbieżność jest wtedy, gdy kod może działać niezależnie od innego kodu. Paralelizm jest wykonywaniem współbieżnego kodu jednocześnie. Równoległość jest szczególnie przydatna, gdy jest dużo pracy do zrobienia w przestrzeni użytkownika, a to zazwyczaj jest ciężkie dla procesora. Współbieżność jest przydatna w rozwiązywaniu problemów, umożliwiając łatwiejsze planowanie i zarządzanie różnymi częściami równolegle.

Greenlets naprawdę błyszczy w sieci programowanie, w którym interakcje z jednym gniazdem mogą występować niezależnie od interakcji z innymi gniazdami. Jest to klasyczny przykład współbieżności. Ponieważ każdy greenlet działa w swoim własnym kontekście, można nadal używać synchronicznych interfejsów API bez gwintowania. Jest to dobre, ponieważ wątki są bardzo drogie pod względem pamięci wirtualnej i narzutu jądra, więc współbieżność, którą można osiągnąć z wątkami, jest znacznie mniejsza. Dodatkowo, threading w Pythonie jest droższy i bardziej ograniczony niż to co zwykle przez Gila. Alternatywą dla współbieżności są zwykle projekty takie jak Twisted, libevent, libuv, node.js itp., gdzie cały Twój kod ma ten sam kontekst wykonania i rejestruje procedury obsługi zdarzeń.

Doskonałym pomysłem jest użycie greenletów (z odpowiednim wsparciem sieciowym, np. przez gevent) do pisania proxy, ponieważ obsługa żądań jest w stanie wykonywać się niezależnie i powinna być napisana jako taka.

Greenlets zapewniają współbieżność z powodów, dla których podałem wcześniej. Współbieżność nie jest paralelizmem. Ukrywając rejestrację zdarzeń i wykonując planowanie połączeń, które normalnie blokują bieżący wątek, projekty takie jak gevent ujawniają tę współbieżność bez konieczności zmiany na asynchroniczne API i przy znacznie niższych kosztach dla systemu.

 158
Author: Matt Joiner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:41

Biorąc pod uwagę odpowiedź @Max i dodając do niej pewne znaczenie dla skalowania, widać różnicę. Osiągnąłem to, zmieniając adresy URL, które mają być wypełnione w następujący sposób:

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)

Musiałem zrezygnować z wersji wieloprocesowej, ponieważ spadła zanim miałem 500; ale na 10000 iteracji:

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

Więc widać, że istnieje pewna znacząca różnica we I/O przy użyciu gevent

 19
Author: TemporalBeing,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-06 21:16:18

To jest na tyle interesujące, by je przeanalizować. Oto kod do porównania wydajności greenlets vs multiprocessing pool vs 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()

Oto wyniki:

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

Myślę, że greenlet twierdzi, że nie jest związany GIL w przeciwieństwie do biblioteki wielowątkowej. Co więcej, Greenlet doc mówi, że jest on przeznaczony do operacji sieciowych. W przypadku intensywnej pracy sieci przełączanie wątków jest w porządku i widać, że podejście wielowątkowe jest dość szybkie. Również zawsze lepiej jest używać oficjalnych bibliotek Pythona; próbowałem zainstalować greenlet na windows i napotkałem problem z zależnościami dll, więc uruchomiłem ten test na maszynie wirtualnej linux. Zawsze staraj się pisać kod z nadzieją, że działa na dowolnej maszynie.

 9
Author: max,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-12 23:27:33

Poprawiając powyższą odpowiedź @TemporalBeing, greenlety nie są "szybsze" niż wątki i jest błędną techniką programowania, aby spawn 60000 wątków Aby Rozwiązać problem współbieżności, mała Pula wątków jest zamiast tego odpowiednia. Oto bardziej rozsądne porównanie(z mojego postu reddit w odpowiedzi na osoby powołujące się na ten 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)

Oto kilka wyników:

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

Nieporozumienie, jakie wszyscy mają na temat nieblokowania IO z Pythonem, jest przekonanie, że interpreter Pythona może wziąć udział w pracy pobierania wyników z gniazd na dużą skalę szybciej niż same połączenia sieciowe, może zwrócić IO. Chociaż z pewnością jest to prawdą w niektórych przypadkach, nie jest to prawdą tak często, jak ludzie myślą, ponieważ interpreter Pythona jest naprawdę bardzo powolny. W moim blogu tutaj, zilustruję kilka profili graficznych, które pokazują, że nawet dla bardzo prostych rzeczy, jeśli masz do czynienia z ostry i szybki dostęp do sieci rzeczy takie jak bazy danych lub serwery DNS, usługi te mogą wrócić o wiele szybciej niż Kod Pythona może uczestniczyć w wielu tysiącach tych połączeń.

 1
Author: zzzeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-20 14:02:01