ग्रीनलेट बनाम। धागे


141

मैं भूगर्भ और हरियाली के लिए नया हूं। मुझे उनके साथ काम करने के बारे में कुछ अच्छे दस्तावेज मिले, लेकिन किसी ने मुझे इस बात का औचित्य नहीं दिया कि मुझे ग्रीनलेट्स का उपयोग कब और कैसे करना चाहिए!

  • वे वास्तव में क्या अच्छे हैं?
  • क्या प्रॉक्सी सर्वर में उनका उपयोग करना अच्छा है या नहीं?
  • धागे क्यों नहीं?

मुझे इस बारे में निश्चित नहीं है कि मूल रूप से सह-रूटीन होने पर वे हमें कैसे संगति प्रदान कर सकते हैं।


1
@ इमरान यह जावा में greenthreads के बारे में है। मेरा सवाल पायथन में ग्रीनलेट के बारे में है। क्या मैं कुछ भूल रहा हूँ ?
Rsh

अफ़ीक, अजगर में धागे वास्तव में वैश्विक दुभाषिया लॉक के कारण वास्तव में समवर्ती नहीं हैं। तो यह दोनों समाधानों के ओवरहेड की तुलना करने के लिए नीचे उबाल होगा। हालांकि मैं समझता हूं कि अजगर के कई कार्यान्वयन हैं, इसलिए यह उन सभी के लिए लागू नहीं हो सकता है।
२२:१३ बजे २२:१३

3
@didierc CPython (और अब के रूप में PyPy) समानांतर में पायथन (बाइट) कोड की व्याख्या नहीं करेगा (अर्थात, दो अलग सीपीयू कोर पर एक ही समय में वास्तव में शारीरिक रूप से)। हालाँकि, जो कुछ भी पायथन प्रोग्राम नहीं करता है वह जीआईएल के तहत होता है (सामान्य उदाहरण syscalls हैं जिनमें I / O और C फ़ंक्शन शामिल हैं जो जानबूझकर GIL जारी करते हैं), और threading.Threadयह वास्तव में सभी रैम के साथ एक OS थ्रेड है। तो यह वास्तव में काफी आसान नहीं है। वैसे, Jython के पास GIL AFAIK नहीं है और PyPy इससे छुटकारा पाने की कोशिश कर रहा है।

जवाबों:


204

ग्रीनलेट समानता प्रदान करते हैं लेकिन समानता नहीं । जब कोड स्वतंत्र रूप से अन्य कोड चला सकता है। समानांतरवाद एक साथ समवर्ती कोड का निष्पादन है। पैरेलिज्म विशेष रूप से तब उपयोगी होता है जब यूजरस्पेस में बहुत सारे काम होने होते हैं, और यह आमतौर पर सीपीयू-भारी सामान होता है। अलग-अलग हिस्सों को शेड्यूल करने और समानांतर में अधिक आसानी से प्रबंधित करने के लिए, अलग-अलग समस्याओं को तोड़ने के लिए कंसीडर उपयोगी है।

ग्रीनलेट्स वास्तव में नेटवर्क प्रोग्रामिंग में चमकते हैं जहां एक सॉकेट के साथ बातचीत अन्य सॉकेट के साथ बातचीत के स्वतंत्र रूप से हो सकती है। यह संक्षिप्त नाम का एक उत्कृष्ट उदाहरण है। क्योंकि प्रत्येक ग्रीनलेट अपने स्वयं के संदर्भ में चलता है, आप बिना थ्रेडिंग के सिंक्रोनस एपीआई का उपयोग करना जारी रख सकते हैं। यह अच्छा है क्योंकि थ्रेड वर्चुअल मेमोरी और कर्नेल ओवरहेड के संदर्भ में बहुत महंगे हैं, इसलिए थ्रेड्स के साथ आप प्राप्त कर सकते हैं। इसके अतिरिक्त, GIL के कारण पाइथन में थ्रेडिंग अधिक महंगा और सामान्य से अधिक सीमित है। संगामिति के विकल्प आमतौर पर मुड़, कामचलाऊ, libuv, नोड .js आदि जैसी परियोजनाएं हैं, जहां आपके सभी कोड समान निष्पादन संदर्भ साझा करते हैं, और ईवेंट हैंडलर पंजीकृत करते हैं।

प्रॉक्सी लिखने के लिए ग्रीनलेट्स (उचित नेटवर्किंग सहायता जैसे कि जियोवेंट के माध्यम से) का उपयोग करना एक उत्कृष्ट विचार है, क्योंकि आपके अनुरोधों की हैंडलिंग स्वतंत्र रूप से निष्पादित करने में सक्षम है और इसे इस तरह लिखा जाना चाहिए।

ग्रीनलेट्स उन कारणों के लिए सहमति प्रदान करते हैं जो मैंने पहले दिए थे। समरूपता समानता नहीं है। ईवेंट पंजीकरण को छुपाने और कॉल पर आपके लिए शेड्यूलिंग का प्रदर्शन करने से जो सामान्य रूप से वर्तमान थ्रेड को ब्लॉक करेगा, जैसे कि जेंवेंट जैसी परियोजनाएं एक अतुल्यकालिक एपीआई में बदलाव की आवश्यकता के बिना इस संगाम को उजागर करती हैं, और आपके सिस्टम में काफी कम लागत पर।


1
धन्यवाद, सिर्फ दो छोटे प्रश्न: 1) क्या उच्च थ्रूपुट को प्राप्त करने के लिए इस समाधान को मल्टीप्रोसेसिंग के साथ जोड़ना संभव है? 2) मैं अभी भी नहीं जानता कि कभी धागे का उपयोग क्यों करते हैं? क्या हम उन्हें पायथन स्टैंडर्ड लाइब्रेरी में संक्षिप्त और बुनियादी कार्यान्वयन के रूप में मान सकते हैं?
Rsh

6
1) हाँ, बिल्कुल। आपको यह समय से पहले नहीं करना चाहिए, लेकिन इस प्रश्न के दायरे से परे कारकों की एक पूरी गुच्छा के कारण, कई प्रक्रियाओं के अनुरोधों को पूरा करने से आपको उच्चतर थ्रूपुट मिलेगा। 2) OS थ्रेड्स पूर्व निर्धारित हैं, और डिफ़ॉल्ट रूप से पूरी तरह से समानांतर हैं। वे पायथन में डिफ़ॉल्ट हैं क्योंकि पायथन देशी थ्रेडिंग इंटरफ़ेस को उजागर करता है, और थ्रेड आधुनिक ऑपरेटिंग सिस्टम में समानता और संगति दोनों के लिए सबसे अच्छा समर्थित और सबसे कम सामान्य भाजक हैं।
मैट जॉइनर

6
मुझे यह उल्लेख करना चाहिए कि थ्रेड्स संतोषजनक नहीं होने तक आपको ग्रीनलेट्स का उपयोग नहीं करना चाहिए (आमतौर पर ऐसा इसलिए होता है क्योंकि आप जो एक साथ कनेक्शन संभाल रहे हैं, और या तो थ्रेड काउंट या जीआईएल आपको दुःख दे रहा है), और यहां तक ​​कि इसके बाद ही यदि कोई अन्य विकल्प आपके लिए उपलब्ध नहीं है। पायथन मानक पुस्तकालय, और अधिकांश तीसरे पक्ष के पुस्तकालयों से सूत्र के माध्यम से हासिल करने की उम्मीद है, इसलिए यदि आप ग्रीनलेट के माध्यम से प्रदान करते हैं तो आपको अजीब व्यवहार मिल सकता है।
मैट जॉइनर

@MattJoiner मैं नीचे फ़ंक्शन है जो md5 योग की गणना करने के लिए विशाल फ़ाइल को पढ़ता है। मैं इस मामले में तेजी से पढ़ने के लिए जियोवेंट का उपयोग कैसे कर सकता हूं 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()
सौम्या

18

@ मैक्स का उत्तर लेना और स्केलिंग के लिए कुछ प्रासंगिकता जोड़ना, आप अंतर देख सकते हैं। मैंने इसे निम्नानुसार भरे जाने वाले URL को बदलकर हासिल किया:

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 के होने से पहले ही मुझे मल्टीप्रोसेस वर्जन को गिराना पड़ा; लेकिन 10,000 पुनरावृत्तियों पर:

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

तो आप देख सकते हैं कि आई / ओ में कुछ महत्वपूर्ण अंतर है


4
काम पूरा करने के लिए 60000 देशी धागों या प्रक्रियाओं को फुलाना पूरी तरह से गलत है और यह परीक्षण कुछ भी नहीं दिखाता है (क्या आपने gevent.joinall () कॉल?) का समय समाप्त कर लिया था। लगभग 50 थ्रेड्स के थ्रेड पूल का उपयोग करने का प्रयास करें, मेरा उत्तर देखें: stackoverflow.com/a/51932442/34549
zzzeek

9

ऊपर दिए गए @TemporalBeing के उत्तर के लिए सही, थ्रेड्स की तुलना में ग्रीनलेट्स "तेज़" नहीं हैं और यह एक गलत प्रोग्रामिंग तकनीक है कि एक संगामिति समस्या को हल करने के लिए 60000 थ्रेड्स फैलाने के लिए, थ्रेड्स का एक छोटा पूल इसके बजाय उपयुक्त है। यहां एक अधिक उचित तुलना है ( इस एसओ पोस्ट का हवाला देते हुए लोगों के जवाब में मेरे रेडिट पोस्ट से)।

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)

यहाँ कुछ परिणाम हैं:

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

पायथन के साथ गैर-अवरुद्ध आईओ के बारे में सभी को गलतफहमी है यह विश्वास है कि पायथन दुभाषिया नेटवर्क कनेक्शनों की तुलना में तेजी से बड़े पैमाने पर सॉकेट्स से परिणाम प्राप्त करने के काम में शामिल हो सकता है जो स्वयं आईओ को वापस कर सकता है। हालांकि यह कुछ मामलों में निश्चित रूप से सच है, यह लगभग उतना ही सच नहीं है जितना अक्सर लोग सोचते हैं, क्योंकि पायथन दुभाषिया वास्तव में, वास्तव में धीमा है। यहाँ अपने ब्लॉग पोस्ट में , मैं कुछ चित्रमय प्रोफाइलों का वर्णन करता हूँ, जो दर्शाती हैं कि बहुत ही सरल चीजों के लिए, यदि आप डेटाबेस या DNS सर्वर जैसी चीज़ों के लिए कुरकुरा और तेज़ नेटवर्क पहुँच के साथ काम कर रहे हैं, तो वे सेवाएँ पायथन कोड की तुलना में बहुत तेज़ी से वापस आ सकती हैं। उन कनेक्शन के कई हजारों में भाग ले सकते हैं।


8

यह विश्लेषण करने के लिए काफी दिलचस्प है। यहाँ ग्रीनलेट बनाम मल्टीप्रोसेसिंग पूल बनाम मल्टी-थ्रेडिंग के प्रदर्शन की तुलना करने के लिए एक कोड है:

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

यहाँ परिणाम हैं:

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

मुझे लगता है कि ग्रीनलेट का दावा है कि यह मल्टीथ्रेडिंग लाइब्रेरी के विपरीत जीआईएल द्वारा बाध्य नहीं है। इसके अलावा, ग्रीनलेट डॉक्टर का कहना है कि यह नेटवर्क संचालन के लिए है। एक नेटवर्क गहन संचालन के लिए, थ्रेड-स्विचिंग ठीक है और आप देख सकते हैं कि मल्टीथ्रेडिंग दृष्टिकोण बहुत तेज़ है। इसके अलावा अजगर के आधिकारिक पुस्तकालयों का उपयोग करना हमेशा ही असंभव होता है; मैंने खिड़कियों पर ग्रीनलेट लगाने की कोशिश की और एक dll निर्भरता की समस्या का सामना करना पड़ा इसलिए मैंने इस परीक्षण को एक लिनक्स vm पर चलाया। Alway इस आशा के साथ एक कोड लिखने की कोशिश करता है कि यह किसी भी मशीन पर चलता है।


25
ध्यान दें कि getsockbynameओएस स्तर पर परिणाम कम से कम (मेरी मशीन पर कम से कम यह करता है)। जब पहले से अज्ञात या एक्सपायर्ड DNS पर मंगवाया जाता है तो यह वास्तव में एक नेटवर्क क्वेरी करेगा, जिसमें कुछ समय लग सकता है। जब हाल ही में हल किए गए होस्टनाम पर आह्वान किया जाता है तो यह उत्तर को बहुत तेजी से लौटाएगा। नतीजतन, आपकी माप पद्धति यहां त्रुटिपूर्ण है। यह आपके अजीब परिणामों की व्याख्या करता है - जीईवेंट वास्तव में मल्टीथ्रेडिंग की तुलना में बहुत खराब नहीं हो सकता है - दोनों वीएम स्तर पर वास्तव में समानांतर नहीं हैं।
केटी।

1
@KT। यह एक उत्कृष्ट बिंदु है। आपको एक अच्छी तस्वीर प्राप्त करने के लिए कई बार उस परीक्षण को चलाने और साधन, मोड और माध्य लेने की आवश्यकता होगी। यह भी ध्यान दें कि प्रोटोकॉल के लिए राउटर कैश रूट पथ और जहां वे रूट पथ कैशे नहीं करते हैं आप विभिन्न डीएनएस पथ ट्रैफ़िक से अलग अंतराल प्राप्त कर सकते हैं। और डीएनएस सर्वर भारी कैश। टाइम-क्लॉक () का उपयोग करके थ्रेडिंग को मापना बेहतर हो सकता है, जहां नेटवर्क हार्डवेयर पर विलंबता से प्रभावित होने के बजाय सीपीयू चक्रों का उपयोग किया जाता है। यह अन्य OS सेवाओं को समाप्त कर सकता है और आपके माप से समय जोड़ सकता है।
DevPlayer

ओह, और आप उन तीन परीक्षणों के बीच ओएस स्तर पर एक डीएनएस फ्लश चला सकते हैं लेकिन फिर से यह केवल स्थानीय डीएनएस कैशिंग से गलत डेटा को कम करेगा।
DevPlayer

हाँ। इस साफ किए गए संस्करण को चलाना: paste.ubuntu.com/p/pg3KTzT2FG मुझे बहुत समान-ईश बार मिलता है ...using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms
23:18 पर sehe

मुझे लगता है कि OSX डीएनएस कैशिंग कर रहा है, लेकिन लिनक्स पर यह "डिफ़ॉल्ट" बात नहीं है: stackoverflow.com/a/11021207/34549 , इसलिए हाँ, संगामिति के निचले स्तर पर ग्रीनलेट्स इंटरप्रेटर ओवरहेड की वजह से बहुत खराब हैं
zzzeek
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.