Greenlet Vs. İş Parçacığı


141

Gents ve yeşiller için yeniyim. Onlarla nasıl çalışacağım hakkında bazı iyi belgeler buldum, ancak hiçbiri bana broşürleri nasıl ve ne zaman kullanmam gerektiği konusunda gerekçe göstermedi!

  • Gerçekten neyi iyi yapıyorlar?
  • Bunları proxy sunucusunda kullanmak iyi bir fikir mi?
  • Neden iplikler değil?

Emin olmadığım şey, temelde ortak rutinler ise bize nasıl eşzamanlılık sağlayabilecekleri.


1
@Imran Java'daki greenthreads hakkında. Benim sorum Python'daki greenlet hakkında. Bir şey mi kaçırıyorum?
Rsh

Afaik, python'daki evreler küresel yorumlayıcı kilidi nedeniyle gerçekten eşzamanlı değil. Bu nedenle, her iki çözümün yükünü karşılaştırmaya kaynar. Her ne kadar birçok python uygulaması olduğunu anlasam da, bu hepsi için geçerli olmayabilir.
didierc

3
@didierc CPython (ve şimdiki PyPy) Python (bayt) kodunu paralel olarak yorumlamayacaktır (yani, iki fiziksel CPU çekirdeğinde gerçekten fiziksel olarak aynı anda). Bununla birlikte, bir Python programının yaptığı her şey GIL altında değildir (yaygın örnekler GIL'i kasıtlı olarak serbest bırakan I / O ve C işlevleri dahil sistem çağrılarıdır) ve a threading.Threadaslında tüm etkileri olan bir OS iş parçacığıdır. Yani gerçekten o kadar basit değil. Bu arada, Jython'un GIL AFAIK'i yok ve PyPy de ondan kurtulmaya çalışıyor.

Yanıtlar:


204

Greenlets eşzamanlılık sağlar ancak sağlamaz paralelliği. Eşzamanlılık, kodun diğer kodlardan bağımsız olarak çalıştırılabildiği zamandır. Paralellik eşzamanlı kodun eşzamanlı olarak yürütülmesidir. Paralellik özellikle kullanıcı alanında yapılması gereken çok iş olduğunda kullanışlıdır ve bu genellikle CPU ağırlıklı şeylerdir. Eşzamanlılık, farklı parçaların paralel olarak daha kolay planlanmasına ve yönetilmesine olanak tanıyan sorunları parçalamak için kullanışlıdır.

Greenlets, bir soketle etkileşimlerin diğer soketlerle etkileşimlerden bağımsız olarak gerçekleşebileceği ağ programlamasında gerçekten parlar. Bu eşzamanlılığın klasik bir örneğidir. Her bir greenlet kendi bağlamında çalıştığından, senkronize edilmeden senkronize API'ları kullanmaya devam edebilirsiniz. Bu iyidir çünkü iş parçacıkları sanal bellek ve çekirdek yükü açısından çok pahalıdır, bu nedenle iş parçacıklarıyla elde edebileceğiniz eşzamanlılık önemli ölçüde daha azdır. Buna ek olarak, Python'da diş açma GIL nedeniyle normalden daha pahalı ve daha sınırlıdır. Eşzamanlılığa alternatifler genellikle Twisted, libevent, libuv, node.js vb. Gibi tüm kodlarınızın aynı yürütme içeriğini paylaştığı ve olay işleyicilerini kaydettiği projelerdir.

Taleplerin ele alınabilmesi bağımsız olarak yürütülebildiğinden ve bu şekilde yazılması gerektiğinden, vekil yazmak için broşürleri (gevent gibi uygun ağ desteği ile) kullanmak mükemmel bir fikirdir.

Greenlets, daha önce verdiğim nedenler için eşzamanlılık sağlar. Eşzamanlılık paralellik değildir. Etkinlik kaydını gizleyerek ve normalde mevcut iş parçacığını engelleyecek aramalarda sizin için zamanlama gerçekleştirerek, gevent gibi projeler, eşzamanlı olmayan bir API'da değişiklik gerektirmeden ve sisteminizde önemli ölçüde daha az maliyetle bu eşzamanlılığı ortaya çıkarır.


1
Teşekkürler, sadece iki küçük soru: 1) Daha yüksek verim elde etmek için bu çözümü çoklu işlemle birleştirmek mümkün mü? 2) Hala neden iplik kullandığını bilmiyorum? Bunları python standart kütüphanesinde eşzamanlılığın naif ve temel bir uygulaması olarak düşünebilir miyiz?
Rsh

6
1) Evet, kesinlikle. Bunu erken yapmamalısınız, ancak bu sorunun kapsamı dışındaki bir dizi faktör nedeniyle, birden fazla işlem sunma isteğine sahip olmak size daha yüksek verim verecektir. 2) İşletim sistemi iş parçacıkları önceden planlanır ve varsayılan olarak tamamen paralelleştirilir. Python, yerel iş parçacığı arabirimini açığa çıkardığından Python'da varsayılan değerdir ve iş parçacıkları, modern işletim sistemlerinde hem paralellik hem de eşzamanlılık için en iyi desteklenen ve en düşük ortak paydadır.
Matt Joiner

6
Dişler tatmin edici olmayana kadar yeşiller kullanmamanız gerektiğini belirtmeliyim (genellikle bu, işlediğiniz eşzamanlı bağlantıların sayısı nedeniyle oluşur ve iplik sayısı veya GIL size keder verir) ve hatta yalnızca kullanabileceğiniz başka bir seçenek yoksa. Python standart kitaplığı ve çoğu üçüncü taraf kitaplığı, iş parçacıklarıyla eşzamanlılığın elde edilmesini bekler , bu nedenle bunu broşürler aracılığıyla sağlarsanız garip davranışlar elde edebilirsiniz.
Matt Joiner

@MattJoiner Ben md5 toplamını hesaplamak için büyük dosyayı okur aşağıdaki işlevi var. i hızlı okumak için bu durumda gevent nasıl kullanabileceğinizi import hashlib def checksum_md5(filename): md5 = hashlib.md5() with open(filename,'rb') as f: for chunk in iter(lambda: f.read(8192), b''): md5.update(chunk) return md5.digest()
Soumya

18

@ Max'in cevabını alarak ölçeklendirme için ona bir miktar ilgi ekleyerek farkı görebilirsiniz. Doldurulacak URL'leri aşağıdaki gibi değiştirerek bunu başardım:

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)

500 yaşımdan önce düştüğü için çoklu işlem versiyonunu bırakmak zorunda kaldım; ancak 10.000 yinelemede:

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

Gevent kullanarak G / Ç'de önemli bir fark olduğunu görebilirsiniz.


4
işi tamamlamak için 60000 yerel iş parçacığı veya süreç oluşturmak için tamamen yanlıştır ve bu test hiçbir şey göstermez (ayrıca gevent.joinall () çağrısının zaman aşımını da yaptınız mı?). Yaklaşık 50 iş parçacığına sahip bir iplik havuzu kullanmayı deneyin, cevabım bakın: stackoverflow.com/a/51932442/34549
zzzeek

9

Yukarıdaki @TemporalBeing yanıtı için düzeltme, greenlets iş parçacıklarından daha hızlı değildir ve bir eşzamanlılık sorununu çözmek için 60000 iş parçacığı oluşturmak yanlış bir programlama tekniğidir , bunun yerine küçük bir iş parçacığı havuzu uygundur. İşte daha makul bir karşılaştırma ( bu SO yazı alıntı insanlar yanıt olarak benim reddit yazı).

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)

İşte bazı sonuçlar:

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

herkesin Python ile bloke olmama IO'su hakkındaki yanlış anlaşılma, Python yorumlayıcısının ağ bağlantılarının kendilerinden IO döndüğünden daha hızlı bir şekilde yuvalardan sonuç alma çalışmalarına katılabileceği inancıdır. Bu bazı durumlarda kesinlikle doğru olsa da, insanların düşündüğü kadar doğru değildir, çünkü Python yorumlayıcısı gerçekten, gerçekten yavaştır. Benim içinde buraya blog post , sana gevrek ve veritabanları veya DNS sunucularının gibi şeyler hızlı ağ erişimi ile ilgileniyor eğer, hatta çok basit şeyler için olduğunu göstermektedir bazı grafiksel profillerini göstermektedir, bu hizmetleri daha hızlı Python kodu daha çok geri gelebilir bu bağlantıların binlerceine katılabilir.


8

Bu analiz için yeterince ilginç. Çok sayıda iş parçacığı ile çok işlemcili havuz ve çok işlemcili havuz arasındaki performansı karşılaştırmak için bir kod:

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

sonuçlar burada:

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

Greenlet'in çok iş parçacıklı kütüphaneden farklı olarak GIL tarafından bağlı olmadığını iddia ettiğini düşünüyorum. Dahası, Greenlet doc ağ operasyonları için olduğunu söylüyor. Ağ yoğun bir işlem için, iplik geçişi iyidir ve çoklu iş parçacığı yaklaşımının oldukça hızlı olduğunu görebilirsiniz. Ayrıca python'un resmi kütüphanelerini kullanmak her zaman tercih edilir; Ben windows üzerinde greenlet yüklemeyi denedim ve bu yüzden bir linux vm üzerinde bu testi koştu bir dll bağımlılığı sorunu karşılaştı. Alway, herhangi bir makinede çalışması umuduyla bir kod yazmaya çalışın.


25
getsockbynameSonuçları OS düzeyinde önbelleğe aldığını unutmayın (en azından makinemde yapıyor). Daha önce bilinmeyen veya süresi dolmuş bir DNS'de çağrıldığında, aslında biraz zaman alabilecek bir ağ sorgusu gerçekleştirir. Kısa bir süre önce çözülmüş bir ana bilgisayar adına çağrıldığında, yanıt çok daha hızlı dönecektir. Sonuç olarak, ölçüm metodolojiniz burada kusurludur. Bu, garip sonuçlarınızı açıklar - gevent, çoklu iş parçacığından çok daha kötü olamaz - her ikisi de VM düzeyinde gerçekten paralel değildir.
KT.

1
@KT. bu mükemmel bir nokta. Bu testi birçok kez çalıştırmanız ve iyi bir resim elde etmek için araçlar, modlar ve medyanlar almanız gerekir. Ayrıca yönlendiricilerin protokoller için rota yollarını önbelleğe aldıklarını ve rota yollarını önbelleklemedikleri yerlerde farklı dns rota yolu trafiğinden farklı gecikmeler elde edebileceğinizi unutmayın. Ve dns sunucuları ağır önbellek. Ağ donanımına göre gecikmeden etkilenmek yerine cpu döngülerinin kullanıldığı time.clock () yöntemini kullanarak iş parçacığını ölçmek daha iyi olabilir. Bu, diğer OS hizmetlerinin gizlice girmesini ve ölçümlerinizden zaman eklemesini engelleyebilir.
DevPlayer

Oh ve bu üç test arasında işletim sistemi seviyesinde bir dns sifonu çalıştırabilirsiniz, ancak yine de sadece yerel dns önbelleklemesinden gelen yanlış verileri azaltacaktır.
DevPlayer

Evet. Bu temizlenmiş sürümü çalıştırıyor: paste.ubuntu.com/p/pg3KTzT2FG Ben hemen hemen aynı-ish kez olsun ...using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms
sehe

Bence OSX dns önbellekleme yapıyor ama Linux'ta "varsayılan" bir şey değil: stackoverflow.com/a/11021207/34549 , bu yüzden evet, düşük eşzamanlılık seviyesinde yeşil çevirmen yorumlayıcı yükü nedeniyle çok daha kötü
zzzeek
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.