2013-03-21 17 views
91

Jestem nowy w geodach i zieleniach. Znalazłem dobrą dokumentację na temat tego, jak z nimi pracować, ale żaden nie dał mi uzasadnienia, jak i kiedy powinienem używać greenletów!Greenlet Vs. Wątki

  • W czym są naprawdę dobrzy?
  • Czy warto używać ich na serwerze proxy, czy nie?
  • Dlaczego nie wątki?

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

+1

@Imran Chodzi o Green thread w Javie. Moje pytanie dotyczy zieleni w Pythonie. Czy czegoś brakuje? – Rsh

+0

Afaik, wątki w pythonie tak naprawdę nie są współbieżne ze względu na globalną blokadę interpretera. Sprowadzałoby się to do porównywania kosztów obu rozwiązań. Chociaż rozumiem, że istnieje kilka implementacji Pythona, więc może nie dotyczyć ich wszystkich. – didierc

+3

@didierc CPython (i PyPy od teraz) nie interpretuje kodu Pythona (bajt) * równolegle * (czyli fizycznie w tym samym czasie na dwóch różnych rdzeniach procesora). Jednak nie wszystko, co robi program Pythona jest pod GIL (typowe przykłady to syscalls, w tym funkcje we/wy i C, które celowo uwalniają GIL), a 'threading.Thread' jest tak naprawdę wątkiem systemu operacyjnego z wszystkimi konsekwencjami. To naprawdę nie jest takie proste. Nawiasem mówiąc, Jython nie ma GIL AFAIK i PyPy też próbuje się go pozbyć. – delnan

Odpowiedz

142

Zielone kropki zapewniają współbieżność, ale nie są równoległe względem. Współbieżność ma miejsce, gdy kod może działać niezależnie od innego kodu. Równoległość jest jednoczesnym wykonywaniem współbieżnego kodu. Równoległość jest szczególnie użyteczna, gdy jest dużo pracy do wykonania w przestrzeni użytkownika, a to zwykle jest praca z dużą ilością procesorów. Współbieżność przydaje się do rozwiązywania problemów, umożliwiając równoległe planowanie i zarządzanie różnymi częściami.

Zielone lasy naprawdę świecą w programowaniu sieciowym, gdzie interakcje z jednym gniazdem mogą odbywać się niezależnie od interakcji z innymi gniazdami. Jest to klasyczny przykład współbieżności. Ponieważ każda greenlet działa we własnym kontekście, możesz nadal korzystać z synchronicznych interfejsów API bez nawlekania. Jest to dobre, ponieważ wątki są bardzo drogie pod względem pamięci wirtualnej i narzutów jądra, więc współbieżność, którą można osiągnąć za pomocą wątków, jest znacznie mniejsza. Dodatkowo, wątki w Pythonie są droższe i bardziej ograniczone niż zwykle dzięki GIL. Alternatywą dla współbieżności są zwykle projekty takie jak Twisted, libevent, libuv, node.js itd., Gdzie cały kod dzieli ten sam kontekst wykonania i rejestrują funkcje obsługi zdarzeń.

To doskonały pomysł, aby używać proxy (z odpowiednim wsparciem sieciowym, np. Poprzez gevent) do pisania proxy, ponieważ obsługa żądań może być wykonywana niezależnie i powinna być napisana jako taka.

Zieleniele zapewniają współbieżność z powodów podanych wcześniej. Współbieżność nie jest równoległością. Ukrywając rejestrację zdarzeń i wykonując dla ciebie harmonogramy połączeń, które normalnie blokują bieżący wątek, projekty takie jak gevent wystawiają tę współbieżność bez potrzeby zmiany na asynchroniczny interfejs API, a przy znacznie mniejszych kosztach dla twojego systemu.

+1

Dzięki, tylko dwa małe pytania: 1) Czy możliwe jest połączenie tego rozwiązania z procesem wieloprocesowym w celu uzyskania wyższej przepustowości? 2) Wciąż nie wiem, dlaczego używasz wątków? Czy możemy uznać je za naiwną i podstawową implementację współbieżności w standardowej bibliotece Pythona? – Rsh

+6

1) Tak, absolutnie. Nie powinieneś robić tego przedwcześnie, ale z powodu wielu czynników wykraczających poza zakres tego pytania, obsługa wielu procesów może zapewnić wyższą przepustowość. 2) Wątki systemu operacyjnego są planowane z wyprzedzeniem i są w pełni zrównoleglone domyślnie. Są one domyślne w Pythonie, ponieważ Python udostępnia interfejs natywnego wątkowania, a wątki są najlepiej obsługiwanym i najniższym wspólnym mianownikiem dla równoległości i współbieżności w nowoczesnych systemach operacyjnych. –

+6

Powinienem wspomnieć, że nie powinieneś nawet używać greenletów, dopóki nic nie będzie zadowalające (zwykle dzieje się tak ze względu na liczbę jednoczesnych połączeń, którymi się posługujesz, i albo liczba wątków, albo GIL dają ci żal), i nawet wtedy, gdy nie ma dla ciebie innej opcji. Standardowa biblioteka Pythona i większość bibliotek trzecich * oczekuje, że * współbieżność zostanie osiągnięta przez wątki, więc możesz uzyskać dziwne zachowanie, jeśli zapewnisz to przez zielone środowisko. –

10

Jest wystarczająco interesująca analiza. Oto kod, aby porównać skuteczność greenlets kontra wieloprocesorowe basen kontra wielowątkowości:

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ązana GIL przeciwieństwie biblioteka wielowątkowa. Co więcej, Greenlet doc mówi, że jest przeznaczony do operacji sieciowych. W przypadku operacji intensywnej w sieci przełączanie wątków jest w porządku i można zauważyć, że podejście wielowątkowe jest dość szybkie. Również zawsze można używać oficjalnych bibliotek Pythona; Próbowałem zainstalować greenlet na windows i napotkałem problem zależności dll, więc uruchomiłem ten test na vm w Linuksie. Zawsze próbuj napisać kod z nadzieją, że działa on na dowolnym komputerze.

+17

Zauważ, że '' getsockbyname'' buforuje wyniki na poziomie systemu operacyjnego (przynajmniej na moim komputerze). Po wywołaniu na nieznanym wcześniej lub wygasłym DNS, wykona on zapytanie sieciowe, co może trochę potrwać. Po wywołaniu na ostatnio rozpoznanej nazwie hosta zwróci odpowiedź znacznie szybciej. W związku z tym twoja metodologia pomiaru jest wadliwa. To wyjaśnia twoje dziwne wyniki - gevent nie może być tak naprawdę gorszy od wielowątkowości - oba nie są naprawdę równoległe na poziomie VM. –

+0

@KT. to doskonały punkt. Trzeba by uruchomić ten test wiele razy i wziąć środki, tryby i mediany, aby uzyskać dobry obraz. Należy również zauważyć, że routery buforują ścieżki tras dla protokołów i tam, gdzie nie buforują ścieżek tras, można uzyskać różne opóźnienia w stosunku do różnych ścieżek trasy dns. A serwery dns są silnie buforowane. Lepszym rozwiązaniem może być pomiar wątków za pomocą metody time.clock(), w której używa się cykli procesora zamiast opóźnień w stosunku do sprzętu sieciowego. Może to wyeliminować wkradanie się innych usług systemu operacyjnego i dodanie czasu z pomiarów. – DevPlayer

+0

Aha i można uruchomić spłukiwanie dns na poziomie systemu operacyjnego pomiędzy tymi trzema testami, ale znowu zredukowałoby to tylko fałszywe dane z lokalnego buforowania dns. – DevPlayer

18

Przyjęcie odpowiedzi @ Max i dodanie do niej pewnego znaczenia dla skalowania, widać różnicę. I to osiągnąć poprzez zmianę adresów należy wypełnić 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 wycofać się z wersji wieloprocesowej gdyż padł przed miałem 500; ale przy 10000 iteracji:

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

Więc widać jest jakaś znacząca różnica w I/O za pomocą gevent