कैसे वास्तव में काम करता है asyncio?


120

यह प्रश्न मेरे दूसरे प्रश्न से प्रेरित है: सीडीएफ में प्रतीक्षा कैसे करें?

वेब पर asyncioबहुत सारे लेख और ब्लॉग पोस्ट हैं , लेकिन वे सभी बहुत ही सतही हैं। मुझे इस बारे में कोई जानकारी नहीं मिली कि asyncioवास्तव में इसे कैसे लागू किया जाता है और I / O को अतुल्यकालिक क्या बनाता है। मैं स्रोत कोड को पढ़ने की कोशिश कर रहा था, लेकिन यह उच्चतम ग्रेड सी कोड की हजारों लाइनें नहीं है, जिनमें से बहुत कुछ सहायक वस्तुओं से संबंधित है, लेकिन सबसे महत्वपूर्ण बात यह है कि पायथन सिंटैक्स और इसे किस कोड में अनुवाद करना मुश्किल है में।

Asycnio का अपना प्रलेखन और भी कम सहायक है। यह कैसे काम करता है, इसके बारे में कोई जानकारी नहीं है, इसका उपयोग करने के बारे में केवल कुछ दिशानिर्देश हैं, जो कभी-कभी भ्रामक / बहुत खराब तरीके से लिखे गए हैं।

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

  1. प्रपत्र की प्रक्रिया परिभाषाओं async def foo(): ...को वास्तव में विरासत में मिली कक्षा के तरीकों के रूप में व्याख्या की जाती है coroutine
  2. शायद, async defवास्तव में awaitबयानों द्वारा कई तरीकों में विभाजित किया गया है, जहां वस्तु, जिस पर इन विधियों को कहा जाता है, वह अब तक निष्पादन के माध्यम से हुई प्रगति का ट्रैक रखने में सक्षम है।
  3. यदि उपरोक्त सत्य है, तो, अनिवार्य रूप से, कुछ वैश्विक प्रबंधक (लूप?) द्वारा कॉरटाइन ऑब्जेक्ट के कॉलिंग तरीकों के लिए एक कॉरटीन का निष्पादन होता है।
  4. वैश्विक प्रबंधक किसी तरह (कैसे?) के बारे में जानते हैं, जब I / O संचालन पायथन (केवल?) कोड द्वारा किया जाता है और वर्तमान निष्पादन विधि त्यागने के बाद लंबित विधि में से एक का चयन करने में सक्षम है ( awaitकथन पर हिट) )।

दूसरे शब्दों में, यहाँ कुछ asyncioवाक्य रचना में "अवरोह" का मेरा प्रयास कुछ अधिक समझने योग्य है:

async def coro(name):
    print('before', name)
    await asyncio.sleep()
    print('after', name)

asyncio.gather(coro('first'), coro('second'))

# translated from async def coro(name)
class Coro(coroutine):
    def before(self, name):
        print('before', name)

    def after(self, name):
        print('after', name)

    def __init__(self, name):
        self.name = name
        self.parts = self.before, self.after
        self.pos = 0

    def __call__():
        self.parts[self.pos](self.name)
        self.pos += 1

    def done(self):
        return self.pos == len(self.parts)


# translated from asyncio.gather()
class AsyncIOManager:

    def gather(*coros):
        while not every(c.done() for c in coros):
            coro = random.choice(coros)
            coro()

क्या मेरा अनुमान सही साबित होना चाहिए: फिर मुझे एक समस्या है। इस परिदृश्य में I / O वास्तव में कैसे होता है? एक अलग धागे में? क्या पूरा दुभाषिया निलंबित है और मैं / हे दुभाषिया के बाहर होता है? I / O का वास्तव में क्या मतलब है? अगर मेरी पायथन प्रक्रिया को सी प्रक्रिया कहा जाता है open(), और यह बदले में कर्नेल को रुकावट भेजती है, तो इस पर नियंत्रण करने के लिए, पायथन दुभाषिया को इस बारे में कैसे पता चलता है और कुछ अन्य कोड को जारी रखने में सक्षम है, जबकि कर्नेल कोड वास्तविक I / O और जब तक है। यह पायथन प्रक्रिया को जगाता है जो मूल रूप से रुकावट भेजती है? सिद्धांत रूप में पायथन दुभाषिया कैसे हो सकता है, इस बारे में पता होना चाहिए?


2
अधिकांश तर्क ईवेंट लूप कार्यान्वयन द्वारा नियंत्रित किए जाते हैं। CPython कैसे BaseEventLoopकार्यान्वित किया जाता है इसे देखें: github.com/python/cpython/blob/…
Blender

@ ब्लेंडर ठीक है, मुझे लगता है कि मुझे अंत में वही मिला जो मैं चाहता था, लेकिन अब मुझे यह समझ नहीं आ रहा है कि कोड को जिस तरह से लिखा गया था। ऐसा क्यों है _run_once, जो वास्तव में "निजी" बने इस पूरे मॉड्यूल में एकमात्र उपयोगी कार्य है? कार्यान्वयन भयानक है, लेकिन यह एक समस्या से कम है। ईवेंट लूप पर कॉल करने के लिए एकमात्र फ़ंक्शन क्यों है जिसे "मुझे कॉल न करें" के रूप में चिह्नित किया गया है?
wvxvw

मेलिंग सूची के लिए यह एक प्रश्न है। किस मामले का उपयोग करने के लिए आपको _run_onceपहली जगह को छूने की आवश्यकता होगी ?
ब्लेंडर

8
यह वास्तव में मेरे सवाल का जवाब नहीं है, हालांकि। कैसे आप किसी भी उपयोगी समस्या को हल करेंगे _run_once? asyncioजटिल है और इसके दोष हैं, लेकिन कृपया चर्चा सिविल रखें। कोड के पीछे डेवलपर्स को बुरा मत समझो जो आप खुद नहीं समझते हैं।
ब्लेंडर

1
@ user8371915 यदि आपको लगता है कि मेरे द्वारा कवर नहीं की गई कोई बात है, तो आप मेरा उत्तर जोड़ने या टिप्पणी करने के लिए स्वागत करते हैं।
भारेल

जवाबों:


203

कैसे काम करता है asyncio?

इस प्रश्न का उत्तर देने से पहले हमें कुछ आधार शब्दों को समझने की आवश्यकता है, यदि आप उनमें से किसी को भी जानते हैं तो उन्हें छोड़ दें।

जेनरेटर

जेनरेटर ऐसी वस्तुएं हैं जो हमें एक अजगर फ़ंक्शन के निष्पादन को निलंबित करने की अनुमति देती हैं। प्रयोक्ता क्यूरेट जनरेटर कीवर्ड का उपयोग करके कार्यान्वित किया जाता है yieldyieldकीवर्ड युक्त एक सामान्य फ़ंक्शन बनाकर , हम उस फ़ंक्शन को जनरेटर में बदलते हैं:

>>> def test():
...     yield 1
...     yield 2
...
>>> gen = test()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

जैसा कि आप देख सकते हैं, next()जनरेटर पर कॉल करने से दुभाषिया परीक्षण के फ्रेम को लोड करने और yieldएड मान वापस करने का कारण बनता है । next()फिर से कॉल करना, फ़्रेम को दुभाषिया स्टैक में फिर से लोड करने का कारण बनता है, और yieldदूसरे मूल्य पर जारी रहता है ।

तीसरी बार next()कहा जाता है, हमारे जनरेटर समाप्त हो गया था, और StopIterationफेंक दिया गया था।

एक जनरेटर के साथ संचार

जनरेटर की एक कम-ज्ञात विशेषता, तथ्य यह है कि आप दो तरीकों का उपयोग करके उनके साथ संवाद कर सकते हैं: send()और throw()

>>> def test():
...     val = yield 1
...     print(val)
...     yield 2
...     yield 3
...
>>> gen = test()
>>> next(gen)
1
>>> gen.send("abc")
abc
2
>>> gen.throw(Exception())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
Exception

कॉल करने पर gen.send(), मान को yieldकीवर्ड से वापसी मान के रूप में पारित किया जाता है ।

gen.throw()दूसरी ओर, जनरेटर के अंदर अपवाद को फेंकने की अनुमति देता है, उसी स्थान पर उठाए गए अपवाद yieldको बुलाया गया था।

जनरेटर से मान लौटाना

एक जनरेटर से एक मूल्य लौटाते हुए, मूल्य को StopIterationअपवाद के अंदर रखा जा रहा है। हम बाद में अपवाद से मूल्य को पुनर्प्राप्त कर सकते हैं और इसे अपनी आवश्यकता के लिए उपयोग कर सकते हैं।

>>> def test():
...     yield 1
...     return "abc"
...
>>> gen = test()
>>> next(gen)
1
>>> try:
...     next(gen)
... except StopIteration as exc:
...     print(exc.value)
...
abc

निहारना, एक नया कीवर्ड: yield from

पायथन 3.4 एक नए कीवर्ड के साथ आया है yield from:। क्या उस कीवर्ड हमें क्या करने की अनुमति देता है, किसी पर पारित है next(), send()और throw()एक भीतरी सबसे नेस्टेड जनरेटर में। यदि आंतरिक जनरेटर एक मान लौटाता है, तो यह भी वापसी मूल्य है yield from:

>>> def inner():
...     inner_result = yield 2
...     print('inner', inner_result)
...     return 3
...
>>> def outer():
...     yield 1
...     val = yield from inner()
...     print('outer', val)
...     yield 4
...
>>> gen = outer()
>>> next(gen)
1
>>> next(gen) # Goes inside inner() automatically
2
>>> gen.send("abc")
inner abc
outer 3
4

मैंने इस विषय पर विस्तार से जानने के लिए एक लेख लिखा है ।

यह सब एक साथ डालें

yield fromपायथॉन 3.4 में नए कीवर्ड को पेश करने पर , हम अब जनरेटर के अंदर जनरेटर बनाने में सक्षम थे कि एक सुरंग की तरह, आंतरिक-सबसे बाहरी से सबसे जनरेटर तक डेटा को आगे और पीछे से पास करें। इसने जनरेटर के लिए एक नया अर्थ पैदा किया है - कोरटाइन

Coroutines ऐसे कार्य हैं जिन्हें चलाया जा सकता है और चलाया जा सकता है। पायथन में, उन्हें async defकीवर्ड का उपयोग करके परिभाषित किया गया है । बहुत जनरेटर की तरह, वे भी अपने स्वयं के प्रारूप का उपयोग करें yield from, जो await। पहले asyncऔर awaitपायथन 3.5 में पेश किए जाने के बाद, हमने ठीक उसी तरह से कोरआउट बनाए थे, जैसे जनरेटर बनाए गए थे ( yield fromइसके बजाय await)।

async def inner():
    return 1

async def outer():
    await inner()

__iter__()विधि को लागू करने वाले प्रत्येक पुनरावृत्ति या जनरेटर की तरह, कोरटाइन लागू होते हैं __await__()जो उन्हें हर समय जारी रखने की अनुमति देता await coroहै।

पायथन डॉक्स के अंदर एक अच्छा अनुक्रम आरेख है जिसे आपको जांचना चाहिए।

Asyncio में, coroutine फ़ंक्शन के अलावा, हमारे पास 2 महत्वपूर्ण ऑब्जेक्ट हैं: कार्य और वायदा

फ्यूचर्स

फ्यूचर्स ऐसी वस्तुएं हैं जिनके पास __await__()विधि लागू है, और उनका काम एक निश्चित स्थिति और परिणाम को पकड़ना है। राज्य निम्नलिखित में से एक हो सकता है:

  1. PENDING - भविष्य का कोई परिणाम या अपवाद सेट नहीं होता है।
  2. रद्द - भविष्य का उपयोग कर रद्द कर दिया गया था fut.cancel()
  3. समाप्त - भविष्य समाप्त हो गया था, या तो परिणाम सेट का उपयोग करके fut.set_result()या अपवाद सेट का उपयोग करकेfut.set_exception()

परिणाम, जैसे आपने अनुमान लगाया है, या तो एक पायथन ऑब्जेक्ट हो सकता है, जिसे वापस कर दिया जाएगा, या एक अपवाद जो उठाया जा सकता है।

वस्तुओं की एक और महत्वपूर्ण विशेषता futureयह है कि उनमें एक विधि होती है जिसे कहा जाता है add_done_callback()। यह विधि कार्य पूरा होते ही कॉल करने की अनुमति देती है - चाहे उसने अपवाद उठाया हो या समाप्त हो गया हो।

कार्य

टास्क ऑब्जेक्ट विशेष फ्यूचर होते हैं, जो कोरटाइन के चारों ओर लपेटते हैं, और आंतरिक-सबसे और बाहरी-सबसे कॉरआउट के साथ संवाद करते हैं। हर बार एक coroutine awaitsa भविष्य, भविष्य को सभी तरह से कार्य पर वापस भेज दिया जाता है (जैसा कि yield from), और कार्य इसे प्राप्त करता है।

इसके बाद, कार्य भविष्य के लिए बाध्य करता है। यह add_done_callback()भविष्य पर कॉल करके ऐसा करता है । अब से, यदि भविष्य में कभी किया जाएगा, तो या तो रद्द होने से, एक अपवाद पारित कर दिया या एक पायथन ऑब्जेक्ट को पारित कर दिया, परिणामस्वरूप, कार्य का कॉलबैक कहा जाएगा, और यह वापस अस्तित्व में आ जाएगा।

Asyncio

अंतिम जलते प्रश्न का हमें उत्तर देना चाहिए - IO कैसे कार्यान्वित किया जाता है?

Asyncio के अंदर गहरी, हमारे पास एक इवेंट लूप है। कार्यों का एक ईवेंट लूप। इवेंट लूप का काम हर बार तैयार होने वाले कार्यों को कॉल करना और एक ही काम करने वाली मशीन में उस प्रयास को समन्वित करना है।

इवेंट लूप का IO भाग एक एकल महत्वपूर्ण फ़ंक्शन पर बनाया गया है जिसे कहा जाता है select। चयन एक अवरुद्ध कार्य है, जिसे ऑपरेटिंग सिस्टम द्वारा कार्यान्वित किया जाता है, जो आने वाले या बाहर जाने वाले डेटा के लिए सॉकेट्स पर प्रतीक्षा करने की अनुमति देता है। डेटा प्राप्त होने पर यह जाग जाता है, और उन सॉकेट्स को लौटा देता है जो डेटा प्राप्त करते हैं, या सॉकेट्स जो लिखने के लिए तैयार हैं।

जब आप asyncio के माध्यम से सॉकेट पर डेटा प्राप्त करने या भेजने का प्रयास करते हैं, तो वास्तव में नीचे क्या होता है, सॉकेट को पहले चेक किया जाता है यदि इसमें कोई डेटा है जिसे तुरंत पढ़ा या भेजा जा सकता है। अगर अपने .send()बफर भरा हुआ है, या .recv()बफर खाली है, सॉकेट के लिए पंजीकृत है selectसमारोह (बस, सूची में से एक में जोड़कर rlistके लिए recvऔर wlistके लिए send) और उचित समारोह awaitसा नव निर्मित futureवस्तु, कि सॉकेट से बंधा।

जब सभी उपलब्ध कार्य वायदा की प्रतीक्षा कर रहे हैं, तो ईवेंट लूप कॉल selectऔर प्रतीक्षा करता है। जब सॉकेट्स में से एक में इनकमिंग डेटा होता है, या इसका sendबफर निकल जाता है, तो उस सॉकेट से जुड़ी भविष्य की वस्तु के लिए asyncio चेक करता है और इसे सेट करने के लिए सेट करता है।

अब सारा जादू होता है। भविष्य पूरा होने के लिए तैयार है, वह कार्य जो add_done_callback()जीवन में वापस आने से पहले खुद .send()को जोड़ता था , और कोरटाइन पर कॉल करता है जो आंतरिक-सबसे कोरआउट ( awaitश्रृंखला के कारण) को फिर से शुरू करता है और आप पास के बफर से नए प्राप्त आंकड़ों को पढ़ते हैं। तक फैल गया था।

विधि श्रृंखला फिर से, के मामले में recv():

  1. select.select प्रतीक्षा करता है।
  2. डेटा के साथ एक तैयार सॉकेट लौटाया जाता है।
  3. सॉकेट से डेटा को एक बफर में स्थानांतरित किया जाता है।
  4. future.set_result() कहा जाता है।
  5. टास्क जो अपने आप जुड़ गया add_done_callback()वह अब जाग गया है।
  6. टास्क .send()कॉरटीन पर कॉल करता है जो सभी तरह से आंतरिक-सबसे कॉरआउट में जाता है और इसे जगाता है।
  7. डेटा को बफ़र से पढ़ा जा रहा है और हमारे विनम्र उपयोगकर्ता पर वापस आ गया है।

सारांश में, asyncio जनरेटर क्षमताओं का उपयोग करता है, जो रुकने और फिर से शुरू करने की अनुमति देता है। यह उन yield fromक्षमताओं का उपयोग करता है जो आंतरिक-सबसे जनरेटर से बाहरी-सबसे अधिक डेटा को आगे-पीछे करने की अनुमति देता है। यह उन सभी का उपयोग करता है जो फ़ंक्शन निष्पादन को रोकते हैं, जबकि यह IO के पूरा होने का इंतजार कर रहा है (ओएस selectफ़ंक्शन का उपयोग करके )।

और सब से अच्छा? जबकि एक फ़ंक्शन को रोक दिया जाता है, दूसरा चल सकता है और नाजुक कपड़े के साथ इंटरलेव कर सकता है, जो कि एसिंसीओ है।


12
यदि किसी और स्पष्टीकरण की आवश्यकता है, तो टिप्पणी करने में संकोच न करें। Btw, मुझे पूरी तरह से यकीन नहीं है कि अगर मुझे यह एक ब्लॉग लेख या स्टैकओवरफ़्लो में जवाब के रूप में लिखना चाहिए था। प्रश्न उत्तर देने के लिए एक लंबा है।
भारेल

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

2
@ user8371915 हमेशा मदद के लिए यहां :-) ध्यान रखें कि एसिंको को समझने के लिए आपको पता होना चाहिए कि जनरेटर, जनरेटर संचार और yield fromकाम कैसे करते हैं। हालाँकि मैंने इस बात पर ध्यान दिया है कि पाठक के बारे में पहले से ही पता होने पर यह संदेहजनक है :-) आपको जो भी मानना ​​है मुझे इसमें कुछ और जोड़ना चाहिए?
भारेल

2
Asyncio से पहले की चीजें शायद सबसे महत्वपूर्ण हैं, क्योंकि वे केवल एक चीज है जो भाषा वास्तव में खुद से करती है। selectसाथ ही अर्हता प्राप्त कर सकते है, क्योंकि यह है कि कैसे गैर अवरुद्ध आई / ओ प्रणाली ओएस पर काम कहता है। वास्तविक asyncioनिर्माण और ईवेंट लूप इन चीजों से बने ऐप-स्तरीय कोड हैं।
मिस्टरमियागी

3
इस पोस्ट में पायथन में अतुल्यकालिक I / O की रीढ़ की जानकारी है। इस तरह के स्पष्टीकरण के लिए धन्यवाद।
mjkim

83

के बारे में बात कर रहा है async/awaitऔर asyncioएक ही बात नहीं है। पहला एक मौलिक, निम्न-स्तरीय निर्माण (कॉरटाइन्स) है, जबकि बाद में इन निर्माणों का उपयोग करने वाला एक पुस्तकालय है। इसके विपरीत, एक भी अंतिम उत्तर नहीं है।

निम्नलिखित कैसे का एक सामान्य वर्णन है async/awaitऔर asyncioतरह पुस्तकालयों काम करते हैं। यही है, शीर्ष पर अन्य चालें हो सकती हैं (वहाँ हैं ...) लेकिन वे असंगत हैं जब तक कि आप उन्हें स्वयं नहीं बनाते हैं। अंतर तब तक नगण्य होना चाहिए जब तक कि आप पहले से ही पर्याप्त नहीं जानते कि इस तरह के प्रश्न को पूछना नहीं है।

1. अखरोट के खोल में कोराटाइन्स बनाम सबरूटीन्स

वैसे ही जैसे सबरूटीन्स (काम करता है, प्रक्रियाओं, ...), coroutines (जनरेटर, ...) कॉल स्टैक और अनुदेश सूचक का एक अमूर्त हैं: कोड के टुकड़े को क्रियान्वित करने का एक ढेर है, और हर एक अनुदेश पर है।

defबनाम async defका अंतर केवल स्पष्टता के लिए है। वास्तविक अंतर returnबनाम है yield। इससे, awaitया yield fromअलग-अलग कॉल से पूरे स्टाॅक में अंतर करें।

1.1। सबरूटीन्स

एक सबरूटीन स्थानीय चर रखने के लिए एक नए स्टैक स्तर का प्रतिनिधित्व करता है, और अंत तक पहुंचने के लिए अपने निर्देशों का एक एकल ट्रावेल। इस तरह से एक सबरूटीन पर विचार करें:

def subfoo(bar):
     qux = 3
     return qux * bar

जब आप इसे चलाते हैं, तो इसका मतलब है

  1. के लिए स्टैक स्थान आवंटित करें barऔरqux
  2. पहले कथन को पुनरावर्ती रूप से निष्पादित करें और अगले कथन पर जाएं
  3. एक बार एक बार return, कॉलिंग स्टैक के लिए इसके मूल्य को धक्का दें
  4. स्टैक को साफ़ करें (1.) और निर्देश सूचक (2.)

विशेष रूप से, 4. का अर्थ है कि एक उप-प्रजाति हमेशा एक ही राज्य में शुरू होती है। फ़ंक्शन के लिए विशेष सब कुछ पूरा होने पर खो जाता है। किसी फ़ंक्शन को फिर से शुरू नहीं किया जा सकता है, भले ही उसके बाद निर्देश हों return

root -\
  :    \- subfoo --\
  :/--<---return --/
  |
  V

1.2। लगातार सबरूटीन्स के रूप में कोराटाइन्स

एक कोरआउट एक सबरूटीन की तरह है, लेकिन इसके राज्य को नष्ट किए बिना बाहर निकल सकता है । इस तरह से एक coroutine पर विचार करें:

 def cofoo(bar):
      qux = yield bar  # yield marks a break point
      return qux

जब आप इसे चलाते हैं, तो इसका मतलब है

  1. के लिए स्टैक स्थान आवंटित करें barऔरqux
  2. पहले कथन को पुनरावर्ती रूप से निष्पादित करें और अगले कथन पर जाएं
    1. एक बार एक बार yield, कॉलिंग स्टैक पर इसके मूल्य को धक्का दें, लेकिन स्टैक और निर्देश सूचक को संग्रहीत करें
    2. एक बार फोन करने पर yield, स्टैक और इंस्ट्रक्टर पॉइंटर को रिस्टोर करें और तर्कों को पुश करेंqux
  3. एक बार एक बार return, कॉलिंग स्टैक के लिए इसके मूल्य को धक्का दें
  4. स्टैक को साफ़ करें (1.) और निर्देश सूचक (2.)

2.1 और 2.2 के अतिरिक्त पर ध्यान दें - पूर्वनिर्धारित बिंदुओं पर एक कोरटाइन को निलंबित और फिर से शुरू किया जा सकता है। यह उसी तरह है जैसे किसी अन्य सबरूटीन को कॉल करने के दौरान एक सबरूटीन को कैसे निलंबित किया जाता है। अंतर यह है कि सक्रिय कोरटाइन अपने कॉलिंग स्टैक के लिए कड़ाई से बाध्य नहीं है। इसके बजाय, एक निलंबित कोरआउट एक अलग, पृथक स्टैक का हिस्सा है।

root -\
  :    \- cofoo --\
  :/--<+--yield --/
  |    :
  V    :

इसका मतलब है कि निलंबित कोरआउट्स को स्वतंत्र रूप से संग्रहीत किया जा सकता है या ढेर के बीच स्थानांतरित किया जा सकता है। किसी भी कॉल स्टैक जिसमें कॉरटीन तक पहुंच है, उसे फिर से शुरू करने का निर्णय ले सकता है।

1.3। कॉल स्टैक को ट्रैवर्स करना

अब तक, हमारी कोरआउट केवल कॉल स्टैक के साथ नीचे जाती है yield। एक सबरूटीन नीचे जा सकता है और ऊपर के साथ कॉल स्टैक returnऔर ()। पूर्णता के लिए, कॉलआउट्स को कॉल स्टैक पर जाने के लिए एक तंत्र की भी आवश्यकता होती है। इस तरह से एक coroutine पर विचार करें:

def wrap():
    yield 'before'
    yield from cofoo()
    yield 'after'

जब आप इसे चलाते हैं, तो इसका मतलब है कि यह अभी भी सबरूटीन की तरह स्टैक और इंस्ट्रक्शन पॉइंटर आवंटित करता है। जब यह सस्पेंड होता है, तब भी यह सबरूटीन स्टोर करने जैसा है।

हालाँकि, दोनोंyield from करता है । यह स्टैक और निर्देश सूचक को निलंबित करता है और चलाता है । ध्यान दें कि पूरी तरह से खत्म होने तक निलंबित रहता है । जब भी निलंबित या कुछ भेजा जाता है, सीधे कॉलिंग स्टैक से जुड़ा होता है।wrap cofoowrapcofoocofoocofoo

1.4। सभी तरह से नीचे Coroutines

जैसा कि स्थापित किया गया है, yield fromएक दूसरे मध्यवर्ती के पार दो स्कोप को जोड़ने की अनुमति देता है। जब पुनरावर्ती रूप से लागू किया जाता है, तो इसका मतलब है कि स्टैक के शीर्ष को स्टैक के नीचे से जोड़ा जा सकता है ।

root -\
  :    \-> coro_a -yield-from-> coro_b --\
  :/ <-+------------------------yield ---/
  |    :
  :\ --+-- coro_a.send----------yield ---\
  :                             coro_b <-/

ध्यान दें rootऔर coro_bएक दूसरे के बारे में नहीं जानते। यह कॉलबैक की तुलना में कोरआउट को बहुत अधिक स्वच्छ बनाता है: अभी भी सबरूटीन की तरह 1: 1 के संबंध में निर्मित कोरटाइन। नियमित कॉल बिंदु तक कॉरुटिनेस अपने पूरे मौजूदा निष्पादन को रोकते हैं और फिर से शुरू करते हैं।

विशेष रूप से, rootफिर से शुरू करने के लिए कॉरटाइन्स की एक मनमानी संख्या हो सकती है। फिर भी, यह एक ही समय में एक से अधिक को फिर से शुरू नहीं कर सकता है। एक ही मूल के कोराटाइन समवर्ती होते हैं लेकिन समानांतर नहीं!

1.5। अजगर का asyncऔरawait

स्पष्टीकरण ने अभी तक स्पष्ट रूप से जनरेटर की शब्दावली yieldऔर yield fromशब्दावली का उपयोग किया है - अंतर्निहित कार्यक्षमता समान है। नया Python3.5 वाक्य रचना asyncऔर awaitस्पष्टता के लिए मुख्य रूप से मौजूद है।

def foo():  # subroutine?
     return None

def foo():  # coroutine?
     yield from foofoo()  # generator? coroutine?

async def foo():  # coroutine!
     await foofoo()  # coroutine!
     return None

async forऔर async withक्योंकि आप टूट जाएगा बयान की जरूरत है yield from/awaitनंगे के साथ श्रृंखला forऔर withबयान।

2. एक साधारण घटना पाश की शारीरिक रचना

अपने आप में, एक coroutine को दूसरे coroutine को नियंत्रण देने की कोई अवधारणा नहीं है । यह केवल एक coroutine स्टैक के नीचे कॉल करने वाले को नियंत्रण दे सकता है। यह कॉलर तब किसी अन्य कॉरआउट में जा सकता है और इसे चला सकता है।

कई कोरआउटों का यह रूट नोड आमतौर पर एक ईवेंट लूप है : निलंबन पर, एक कोरआउट एक घटना उत्पन्न करता है, जिस पर वह फिर से शुरू करना चाहता है। बदले में, इवेंट लूप कुशलतापूर्वक इन घटनाओं के होने की प्रतीक्षा करने में सक्षम है। यह यह तय करने की अनुमति देता है कि कौन सा कोरटाइन आगे चल रहा है, या फिर से शुरू होने से पहले कैसे प्रतीक्षा करें।

इस तरह के डिजाइन का अर्थ है कि पूर्व-परिभाषित घटनाओं का एक सेट है जो लूप समझता है। awaitजब तक कोई ईवेंट awaitएड न हो, तब तक एक-दूसरे को कई कॉरआउट करते हैं । यह घटना आईएनजी नियंत्रण के साथ घटना लूप के साथ सीधे संवाद कर सकती है yield

loop -\
  :    \-> coroutine --await--> event --\
  :/ <-+----------------------- yield --/
  |    :
  |    :  # loop waits for event to happen
  |    :
  :\ --+-- send(reply) -------- yield --\
  :        coroutine <--yield-- event <-/

कुंजी यह है कि कोरटाउइन निलंबन इवेंट लूप और घटनाओं को सीधे संवाद करने की अनुमति देता है। मध्यवर्ती कोरआउट स्टैक को किसी भी ज्ञान की आवश्यकता नहीं होती है कि कौन सा लूप इसे चला रहा है, और न ही ईवेंट कैसे काम करते हैं।

2.1.1। समय में घटनाएँ

समय पर पहुंचने के लिए सबसे सरल घटना है। यह थ्रेडेड कोड का एक मौलिक ब्लॉक है: sleepएक शर्त के सच होने तक बार-बार एक धागा । हालांकि, एक नियमित sleepब्लॉक निष्पादन अपने आप होता है - हम चाहते हैं कि अन्य कोरटाइन अवरुद्ध न हों। इसके बजाय, हम इवेंट लूप को तब बताना चाहते हैं जब उसे वर्तमान कोरआउट स्टैक फिर से शुरू करना चाहिए।

2.1.2। एक घटना को परिभाषित करना

एक घटना केवल एक मूल्य है जिसे हम पहचान सकते हैं - यह एक Enum, एक प्रकार या अन्य पहचान के माध्यम से हो सकता है। हम इसे एक साधारण वर्ग के साथ परिभाषित कर सकते हैं जो हमारे लक्ष्य समय को संग्रहीत करता है। घटना की जानकारी संग्रहीत करने के अलावा , हम awaitएक कक्षा को सीधे अनुमति दे सकते हैं।

class AsyncSleep:
    """Event to sleep until a point in time"""
    def __init__(self, until: float):
        self.until = until

    # used whenever someone ``await``s an instance of this Event
    def __await__(self):
        # yield this Event to the loop
        yield self

    def __repr__(self):
        return '%s(until=%.1f)' % (self.__class__.__name__, self.until)

यह वर्ग केवल घटना को संग्रहीत करता है - यह नहीं कहता है कि वास्तव में इसे कैसे संभालना है।

एकमात्र विशेष विशेषता __await__यह है - यह वह है जिसके लिए awaitकीवर्ड दिखता है। व्यावहारिक रूप से, यह एक पुनरावृत्ति है, लेकिन नियमित पुनरावृत्ति मशीनरी के लिए उपलब्ध नहीं है।

2.2.1। किसी घटना की प्रतीक्षा में

अब जब हमारे पास एक ईवेंट है, तो कोरटाइन इस पर कैसे प्रतिक्रिया करते हैं? हमें अपने ईवेंट के sleepद्वारा समतुल्य व्यक्त करने में सक्षम होना चाहिए await। यह देखने के लिए कि क्या चल रहा है, हम आधे समय के लिए दो बार प्रतीक्षा करते हैं:

import time

async def asleep(duration: float):
    """await that ``duration`` seconds pass"""
    await AsyncSleep(time.time() + duration / 2)
    await AsyncSleep(time.time() + duration / 2)

हम सीधे इस तात्कालिकता को चला सकते हैं और चला सकते हैं। जनरेटर के समान, इसका उपयोग coroutine.sendकरने तक कोरटाइन चलाता है yield

coroutine = asleep(100)
while True:
    print(coroutine.send(None))
    time.sleep(0.1)

यह हमें दो AsyncSleepघटनाएँ देता है और फिर StopIterationजब कोरटाइन किया जाता है। ध्यान दें कि केवल देरी time.sleepलूप से है! प्रत्येक AsyncSleepकेवल वर्तमान समय से ऑफसेट संग्रहीत करता है।

2.2.2। घटना + नींद

इस बिंदु पर, हमारे पास अपने निपटान में दो अलग-अलग तंत्र हैं:

  • AsyncSleep ऐसी घटनाएँ जो एक कोरटीन के अंदर से उपज सकती हैं
  • time.sleep यह कोरटाइन को प्रभावित किए बिना इंतजार कर सकता है

विशेष रूप से, ये दोनों ऑर्थोगोनल हैं: कोई भी दूसरे को प्रभावित या ट्रिगर नहीं करता है। परिणामस्वरूप, हम sleepदेरी की पूर्ति के लिए अपनी रणनीति के साथ आ सकते हैं AsyncSleep

2.3। एक भोली घटना लूप

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

यह एक सरल समय-निर्धारण के लिए बनाता है:

  1. उनके इच्छित समय के आधार पर क्राउटइन को क्रमबद्ध करें
  2. पहले उठो जो जगाना चाहता है
  3. इस समय तक प्रतीक्षा करें
  4. इस coroutine को चलाएं
  5. 1 से दोहराएं।

एक तुच्छ कार्यान्वयन को किसी उन्नत अवधारणा की आवश्यकता नहीं है। A listआज तक कोरटाइन को क्रमबद्ध करने की अनुमति देता है। प्रतीक्षा एक नियमित है time.sleep। कोरटाइन चलाना पहले की तरह ही काम करता है coroutine.send

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    # store wake-up-time and coroutines
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting:
        # 2. pick the first coroutine that wants to wake up
        until, coroutine = waiting.pop(0)
        # 3. wait until this point in time
        time.sleep(max(0.0, until - time.time()))
        # 4. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])

बेशक, इसमें सुधार के लिए पर्याप्त जगह है। हम प्रतीक्षा कतार या घटनाओं के लिए प्रेषण तालिका के लिए एक ढेर का उपयोग कर सकते हैं। हम वापसी मूल्यों को भी प्राप्त कर सकते हैं StopIterationऔर उन्हें कोरटाइन को सौंप सकते हैं। हालांकि, मौलिक सिद्धांत एक ही है।

2.4। सहकारी प्रतीक्षा

AsyncSleepघटना और runघटना पाश समय समाप्त हो गया घटनाओं की एक पूरी तरह से काम कर रहा कार्यान्वयन कर रहे हैं।

async def sleepy(identifier: str = "coroutine", count=5):
    for i in range(count):
        print(identifier, 'step', i + 1, 'at %.2f' % time.time())
        await asleep(0.1)

run(*(sleepy("coroutine %d" % j) for j in range(5)))

यह सहक्रियात्मक रूप से पांचों कोरटाइनों के बीच स्विच करता है, प्रत्येक को 0.1 सेकंड के लिए निलंबित करता है। भले ही ईवेंट लूप समकालिक हो, फिर भी यह 2.5 सेकंड के बजाय 0.5 सेकंड में कार्य को निष्पादित करता है। प्रत्येक कोरटाइन राज्य रखता है और स्वतंत्र रूप से कार्य करता है।

3. I / O ईवेंट लूप

एक इवेंट लूप जो समर्थन करता sleepहै वह मतदान के लिए उपयुक्त है । हालाँकि, फ़ाइल हैंडल पर I / O की प्रतीक्षा अधिक कुशलता से की जा सकती है: ऑपरेटिंग सिस्टम I / O को लागू करता है और इस प्रकार जानता है कि कौन सा हैंडल तैयार है। आदर्श रूप से, एक ईवेंट लूप को एक स्पष्ट "आई / ओ" इवेंट के लिए तैयार का समर्थन करना चाहिए।

3.1। selectकॉल

पायथन में पहले से ही I / O हैंडल पढ़ने के लिए OS को क्वेरी करने के लिए एक इंटरफ़ेस है। जब पढ़ने या लिखने के लिए हैंडल के साथ कॉल किया जाता है, तो यह पढ़ने या लिखने के लिए तैयार हैंडल लौटाता है :

readable, writeable, _ = select.select(rlist, wlist, xlist, timeout)

उदाहरण के लिए, हम openलिखने के लिए एक फ़ाइल बना सकते हैं और इसके तैयार होने की प्रतीक्षा कर सकते हैं :

write_target = open('/tmp/foo')
readable, writeable, _ = select.select([], [write_target], [])

एक बार रिटर्न का चयन करें, writeableहमारी खुली फाइल शामिल है।

3.2। मूल I / O घटना

AsyncSleepअनुरोध के समान , हमें I / O के लिए एक घटना को परिभाषित करने की आवश्यकता है। अंतर्निहित selectतर्क के साथ, घटना को एक पठनीय वस्तु का उल्लेख करना चाहिए - एक openफ़ाइल कहो । इसके अलावा, हम स्टोर करते हैं कि कितना डेटा पढ़ना है।

class AsyncRead:
    def __init__(self, file, amount=1):
        self.file = file
        self.amount = amount
        self._buffer = ''

    def __await__(self):
        while len(self._buffer) < self.amount:
            yield self
            # we only get here if ``read`` should not block
            self._buffer += self.file.read(1)
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.file, self.amount, len(self._buffer)
        )

जैसा कि AsyncSleepहम ज्यादातर अंतर्निहित सिस्टम कॉल के लिए आवश्यक डेटा संग्रहीत करते हैं। यह समय, __await__कई बार शुरू होने में सक्षम है - जब तक कि हमारा वांछित amountपढ़ा नहीं गया है। इसके अलावा, हम returnI / O परिणाम को फिर से शुरू करने के बजाय।

3.3। ई / ओ के साथ एक ईवेंट लूप को संवर्धित करना

हमारे ईवेंट लूप का आधार अभी भी runपहले से परिभाषित है। सबसे पहले, हमें रीड रिक्वेस्ट को ट्रैक करना होगा। यह अब एक क्रमबद्ध शेड्यूल नहीं है, हम केवल कोरटाइन के लिए अनुरोध पढ़ते हैं।

# new
waiting_read = {}  # type: Dict[file, coroutine]

चूंकि select.selectटाइमआउट पैरामीटर लिया जाता है, इसलिए हम इसका उपयोग कर सकते हैं time.sleep

# old
time.sleep(max(0.0, until - time.time()))
# new
readable, _, _ = select.select(list(reads), [], [])

यह हमें सभी पठनीय फाइलें देता है - यदि कोई हैं, तो हम संगत कॉरआउट चलाते हैं। यदि कोई नहीं है, तो हमने अपने वर्तमान कोरआउट को चलाने के लिए काफी लंबा इंतजार किया है।

# new - reschedule waiting coroutine, run readable coroutine
if readable:
    waiting.append((until, coroutine))
    waiting.sort()
    coroutine = waiting_read[readable[0]]

अंत में, हमें वास्तव में रीड रिक्वेस्ट के लिए सुनना होगा।

# new
if isinstance(command, AsyncSleep):
    ...
elif isinstance(command, AsyncRead):
    ...

3.4। इसे एक साथ रखना

ऊपर एक सरलीकरण था। अगर हम हमेशा पढ़ सकते हैं, तो हमें नींद से बाहर निकलने वाले तारों को नहीं करने के लिए कुछ स्विचिंग करने की आवश्यकता है। हमें पढ़ने के लिए कुछ नहीं होना चाहिए और न ही कुछ करना चाहिए। हालांकि, अंतिम परिणाम अभी भी 30 एलओसी में फिट बैठता है।

def run(*coroutines):
    """Cooperatively run all ``coroutines`` until completion"""
    waiting_read = {}  # type: Dict[file, coroutine]
    waiting = [(0, coroutine) for coroutine in coroutines]
    while waiting or waiting_read:
        # 2. wait until the next coroutine may run or read ...
        try:
            until, coroutine = waiting.pop(0)
        except IndexError:
            until, coroutine = float('inf'), None
            readable, _, _ = select.select(list(waiting_read), [], [])
        else:
            readable, _, _ = select.select(list(waiting_read), [], [], max(0.0, until - time.time()))
        # ... and select the appropriate one
        if readable and time.time() < until:
            if until and coroutine:
                waiting.append((until, coroutine))
                waiting.sort()
            coroutine = waiting_read.pop(readable[0])
        # 3. run this coroutine
        try:
            command = coroutine.send(None)
        except StopIteration:
            continue
        # 1. sort coroutines by their desired suspension ...
        if isinstance(command, AsyncSleep):
            waiting.append((command.until, coroutine))
            waiting.sort(key=lambda item: item[0])
        # ... or register reads
        elif isinstance(command, AsyncRead):
            waiting_read[command.file] = coroutine

3.5। सहकारी I / ओ

AsyncSleep, AsyncReadऔर runकार्यान्वयन अब पूरी तरह से नींद और / या पढ़ने के लिए कार्य कर रहे हैं। के रूप में ही sleepy, हम एक सहायक को पढ़ने का परीक्षण परिभाषित कर सकते हैं:

async def ready(path, amount=1024*32):
    print('read', path, 'at', '%d' % time.time())
    with open(path, 'rb') as file:
        result = return await AsyncRead(file, amount)
    print('done', path, 'at', '%d' % time.time())
    print('got', len(result), 'B')

run(sleepy('background', 5), ready('/dev/urandom'))

इसे चलाने पर, हम देख सकते हैं कि हमारा I / O वेटिंग कार्य के साथ इंटरलेस्ड है:

id background round 1
read /dev/urandom at 1530721148
id background round 2
id background round 3
id background round 4
id background round 5
done /dev/urandom at 1530721148
got 1024 B

4. गैर-अवरोधक I / O

मैं / फाइलों पर हे अवधारणा भर में हो जाता है, वहीं यह की तरह एक पुस्तकालय के लिए वास्तव में उपयुक्त नहीं है asyncio: selectकॉल हमेशा फ़ाइलों के लिए रिटर्न , और दोनों openऔर readहो सकता है अनिश्चित काल के लिए ब्लॉक । यह ईवेंट लूप के सभी कोरआउट्स को अवरुद्ध करता है - जो खराब है। लाइब्रेरीज़ जैसे aiofilesथ्रेड्स का उपयोग करते हैं और नकली गैर-अवरोधक I / O और फ़ाइल पर ईवेंट को सिंक्रोनाइज़ करते हैं।

हालांकि, सॉकेट्स गैर-अवरुद्ध I / O के लिए अनुमति देते हैं - और उनकी अंतर्निहित विलंबता इसे और अधिक महत्वपूर्ण बनाती है। जब एक घटना लूप में उपयोग किया जाता है, तो डेटा का इंतजार करना और पुन: प्रयास करना कुछ भी अवरुद्ध किए बिना लपेटा जा सकता है।

4.1। गैर-अवरोधक I / O घटना

हमारे समान AsyncRead, हम सॉकेट्स के लिए एक सस्पेंड-एंड-रीड इवेंट को परिभाषित कर सकते हैं। एक फ़ाइल लेने के बजाय, हम एक सॉकेट लेते हैं - जो गैर-अवरुद्ध होना चाहिए। इसके अलावा, हमारे __await__उपयोग के socket.recvबजाय file.read

class AsyncRecv:
    def __init__(self, connection, amount=1, read_buffer=1024):
        assert not connection.getblocking(), 'connection must be non-blocking for async recv'
        self.connection = connection
        self.amount = amount
        self.read_buffer = read_buffer
        self._buffer = b''

    def __await__(self):
        while len(self._buffer) < self.amount:
            try:
                self._buffer += self.connection.recv(self.read_buffer)
            except BlockingIOError:
                yield self
        return self._buffer

    def __repr__(self):
        return '%s(file=%s, amount=%d, progress=%d)' % (
            self.__class__.__name__, self.connection, self.amount, len(self._buffer)
        )

इसके विपरीत AsyncRead, __await__वास्तव में गैर-अवरुद्ध I / O करता है। जब डेटा उपलब्ध होता है, तो यह हमेशा पढ़ता है। जब कोई डेटा उपलब्ध नहीं होता है, तो यह हमेशा निलंबित रहता है। इसका मतलब है कि ईवेंट लूप केवल अवरुद्ध है जब हम उपयोगी कार्य करते हैं।

4.2। इवेंट लूप को अन-ब्लॉक करना

जहां तक ​​इवेंट लूप का सवाल है, कुछ भी ज्यादा नहीं बदलता है। सुनने के लिए घटना अभी भी फ़ाइलों के लिए के रूप में ही है - एक फ़ाइल विवरणक द्वारा चिह्नित तैयार select

# old
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
# new
elif isinstance(command, AsyncRead):
    waiting_read[command.file] = coroutine
elif isinstance(command, AsyncRecv):
    waiting_read[command.connection] = coroutine

इस बिंदु पर, यह स्पष्ट होना चाहिए कि AsyncReadऔर AsyncRecvउसी तरह की घटना है। हम आसानी से उन्हें एक विनिमेय I / O घटक के साथ एक घटना होने के लिए रिफ्लेक्टर कर सकते हैं । वास्तव में, ईवेंट लूप, कोरआउट और ईवेंट्स एक अनुसूचक, मनमाने ढंग से मध्यवर्ती कोड और वास्तविक I / O को अलग-अलग करते हैं।

4.3। गैर-अवरुद्ध I / O का बदसूरत पक्ष

सिद्धांत रूप में, क्या आप इस बिंदु पर क्या करना चाहिए के तर्क को दोहराने है readएक के रूप में recvके लिए AsyncRecv। हालांकि, यह अब बहुत अधिक बदसूरत है - आपको कर्नेल के अंदर फ़ंक्शन ब्लॉक होने पर शुरुआती रिटर्न को संभालना होगा, लेकिन आपको नियंत्रण प्राप्त होगा। उदाहरण के लिए, किसी फ़ाइल को खोलना बनाम कनेक्शन खोलना बहुत लंबा है:

# file
file = open(path, 'rb')
# non-blocking socket
connection = socket.socket()
connection.setblocking(False)
# open without blocking - retry on failure
try:
    connection.connect((url, port))
except BlockingIOError:
    pass

लंबी कहानी छोटी, जो बनी हुई है वह अपवाद की हैंडलिंग की कुछ दर्जन लाइनें हैं। ईवेंट और इवेंट लूप पहले से ही इस बिंदु पर काम करते हैं।

id background round 1
read localhost:25000 at 1530783569
read /dev/urandom at 1530783569
done localhost:25000 at 1530783569 got 32768 B
id background round 2
id background round 3
id background round 4
done /dev/urandom at 1530783569 got 4096 B
id background round 5

परिशिष्ट

उदाहरण कोड github पर


yield selfAsyncSleep में उपयोग करने से मुझे Task got back yieldत्रुटि मिलती है , ऐसा क्यों है? मुझे लगता है कि asyncio.Futures में कोड का उपयोग करता है कि देखते हैं। नंगे उपज का उपयोग ठीक काम करता है।
रॉन सेरुया

1
इवेंट लूप आमतौर पर केवल अपने स्वयं के ईवेंट की अपेक्षा करते हैं। आप आम तौर पर पुस्तकालयों में घटनाओं और इवेंट लूप्स को नहीं मिला सकते हैं; यहां दिखाई गई ईवेंट केवल दिखाए गए इवेंट लूप के साथ काम करती हैं। विशिष्ट रूप से, एसिंको केवल ईवेंट लूप के संकेत के रूप में कोई नहीं (यानी एक नंगे उपज) का उपयोग करता है। इवेंट सीधे इवेंट लूप ऑब्जेक्ट के साथ वेकअप रजिस्टर करने के लिए बातचीत करते हैं।
मिस्टरमियागी

12

आपका coroअवरोह वैचारिक रूप से सही है, लेकिन थोड़ा अधूरा है।

awaitबिना शर्त निलंबित नहीं करता है, लेकिन केवल अगर यह एक अवरुद्ध कॉल का सामना करता है। यह कैसे पता चलता है कि एक कॉल अवरुद्ध है? यह कोड की प्रतीक्षा की जा रही है। उदाहरण के लिए, सॉकेट रीड का एक उचित कार्यान्वयन निम्नलिखित के लिए उपयुक्त हो सकता है:

def read(sock, n):
    # sock must be in non-blocking mode
    try:
        return sock.recv(n)
    except EWOULDBLOCK:
        event_loop.add_reader(sock.fileno, current_task())
        return SUSPEND

वास्तविक asyncio में समतुल्य कोडFuture जादू के मूल्यों के बदले की स्थिति को संशोधित करता है , लेकिन अवधारणा समान है। जब उचित रूप से एक जनरेटर जैसी वस्तु के लिए अनुकूलित किया जाता है, तो उपरोक्त कोड awaitएड हो सकता है ।

कॉलर की ओर, जब आपकी कोरटाइन में शामिल हैं:

data = await read(sock, 1024)

यह कुछ के करीब आता है:

data = read(sock, 1024)
if data is SUSPEND:
    return SUSPEND
self.pos += 1
self.parts[self.pos](...)

जनरेटर से परिचित लोग उपरोक्त का वर्णन करते हैं, yield fromजो निलंबन को स्वचालित रूप से करता है।

सस्पेंशन चेन ईवेंट लूप तक सभी तरह से जारी रहता है, जो यह नोटिस करता है कि कोरटाइन को निलंबित कर दिया गया है, इसे रननेबल सेट से हटा दिया जाता है, और जो चल रहे हैं, तो कोरटाइन को निष्पादित करने के लिए आगे बढ़ता है। यदि कोई कोरटाइन रन करने योग्य नहीं है, तो लूप select()तब तक इंतजार करता है जब तक कि एक फाइल डिस्क्रिप्टर को एक कोरोइन में रुचि न हो, वह IO के लिए तैयार हो जाता है। (ईवेंट लूप एक फ़ाइल-डिस्क्रिप्टर-से-कॉरआउट मैपिंग बनाए रखता है।)

उपर्युक्त उदाहरण में, एक बार select()ईवेंट लूप जो sockपढ़ने योग्य है, उसे coroरननेबल सेट में फिर से जोड़ देगा, इसलिए इसे निलंबन के बिंदु से जारी रखा जाएगा।

दूसरे शब्दों में:

  1. सब कुछ डिफ़ॉल्ट रूप से एक ही थ्रेड में होता है।

  2. ईवेंट लूप कोरआउट्स को शेड्यूल करने और उन्हें जागने के लिए जिम्मेदार है जब वे जो कुछ भी इंतजार कर रहे थे (आमतौर पर एक आईओ कॉल जो सामान्य रूप से ब्लॉक हो जाएगा, या एक टाइमआउट) तैयार हो जाता है।

कोरटाइन-ड्राइविंग इवेंट लूप पर अंतर्दृष्टि के लिए, मैं डेव बेज़ले द्वारा इस बात की सिफारिश करता हूं , जहां वह लाइव ऑडियंस के सामने एक इवेंट लूप को स्क्रैच से कोड करने का प्रदर्शन करता है।


धन्यवाद, यह उसके बाद के करीब है जो मैं कर रहा हूँ, लेकिन, यह अभी भी नहीं समझाता है कि async.wait_for()वह ऐसा क्यों नहीं करता है जो इसे माना जाता है ... इवेंट लूप में कॉलबैक जोड़ने और इसे बताने के लिए यह इतनी बड़ी समस्या क्यों है हालाँकि, आपके द्वारा अभी-अभी जोड़े गए कई कॉलबैक को संसाधित करने की आवश्यकता है? के साथ मेरी निराशा asyncioइस तथ्य के कारण है कि अंतर्निहित अवधारणा बहुत सरल है, और, उदाहरण के लिए, Emacs Lisp ने युगों के लिए कार्यान्वयन किया था, बिना buzzwords का उपयोग किए बिना ... (यानी create-async-processऔर accept-process-output- और यह सब आवश्यक है ... (contd)
wvxvw

10
@wvxvw मैंने आपके द्वारा पोस्ट किए गए प्रश्न का उत्तर देने के लिए जितना किया है, उतना ही संभव है कि यह भी संभव है कि केवल अंतिम पैराग्राफ में छह प्रश्न शामिल हों। और इसलिए हम चलते हैं - यह wait_for ऐसा नहीं है जो यह करने के लिए माना जाता है (यह करता है, यह एक coroutine है जिसे आप प्रतीक्षा करने वाले हैं), यह आपकी अपेक्षाओं से मेल नहीं खाता है जो सिस्टम को डिज़ाइन करने और लागू करने के लिए किया गया है। मुझे लगता है कि अगर आपकी समस्या अलग थ्रेड में चल रही थी, तो आपकी समस्या एसिंकोयो से मेल खा सकती है, लेकिन मुझे आपके उपयोग के मामले का विवरण नहीं पता है और, ईमानदारी से, आपका रवैया आपकी मदद करने में ज्यादा मजेदार नहीं है।
user4815162342

5
@wvxvw My frustration with asyncio is in part due to the fact that the underlying concept is very simple, and, for example, Emacs Lisp had implementation for ages, without using buzzwords...- पायथन के लिए इस सरल अवधारणा के बिना कुछ भी लागू करने से आपको कुछ भी नहीं रोकता है फिर :) आप इस बदसूरत asyncio का उपयोग क्यों करते हैं? अपने खुद के खरोंच से लागू करें। उदाहरण के लिए, आप अपना स्वयं का async.wait_for()फ़ंक्शन बनाने के साथ शुरू कर सकते हैं जो वास्तव में वही करता है जो इसे करना चाहिए था।
मिखाइल गेरेसिमोव

1
@MikhailGerasimov आपको लगता है कि यह एक बयानबाजी का सवाल है। लेकिन, मैं आपके लिए रहस्य को दूर करना चाहता हूं। भाषा दूसरों को बोलने के लिए डिज़ाइन की गई है। मैं दूसरों के लिए नहीं चुन सकता कि वे कौन सी भाषा बोलते हैं, भले ही मुझे लगता है कि वे जिस भाषा को बोलते हैं वह कचरा है, सबसे अच्छा मैं यह कर सकता हूं कि उन्हें समझाने की कोशिश करें। दूसरे शब्दों में, अगर मैं चुनने के लिए स्वतंत्र था, तो मैं शुरुआत करने के लिए कभी भी अजगर का चयन नहीं करूंगा asyncio। लेकिन, सिद्धांत रूप में, यह मेरा निर्णय नहीं है। मैं en.wikipedia.org/wiki/Ultimatum_game के माध्यम से कचरा भाषा का उपयोग करने के लिए मजबूर हूं ।
wvxvw

4

यह उन सभी दो मुख्य चुनौतियों के लिए उबलता है जिन्हें एसिंसीओ संबोधित कर रहा है:

  • एक ही धागे में कई I / O कैसे करें?
  • सहकारी मल्टीटास्किंग कैसे लागू करें?

पहले बिंदु का उत्तर लंबे समय तक रहा है और इसे चुनिंदा लूप कहा जाता है । अजगर में, यह चयनकर्ताओं के मॉड्यूल में लागू किया जाता है

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

इस उत्तर में अधिक संसाधन ।


संपादित करें: गोरोइंटिंस के बारे में अपनी टिप्पणी को संबोधित करते हुए:

Asyncio में एक गोरोइन के बराबर निकटतम वास्तव में एक coroutine नहीं है, लेकिन एक कार्य ( प्रलेखन में अंतर देखें )। अजगर में, एक कोरटाइन (या एक जनरेटर) इवेंट लूप या I / O की अवधारणाओं के बारे में कुछ नहीं जानता है। यह केवल एक ऐसा कार्य है जो yieldअपनी वर्तमान स्थिति को बनाए रखते हुए इसके निष्पादन को रोक सकता है , इसलिए इसे बाद में बहाल किया जा सकता है। yield fromवाक्य रचना के लिए उन्हें एक पारदर्शी तरीके से चेनिंग के लिए अनुमति देता है।

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


संपादित करें: अपनी पोस्ट में कुछ प्रश्नों को संबोधित करते हुए:

इस परिदृश्य में I / O वास्तव में कैसे होता है? एक अलग धागे में? क्या पूरा दुभाषिया निलंबित है और मैं / हे दुभाषिया के बाहर होता है?

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

I / O का वास्तव में क्या मतलब है? अगर मेरी अजगर प्रक्रिया को सी ओपन () प्रक्रिया कहा जाता है, और यह बदले में कर्नेल के लिए रुकावट भेजती है, तो इस पर नियंत्रण करना, पायथन दुभाषिया को इस बारे में कैसे पता चलता है और कुछ अन्य कोड को जारी रखने में सक्षम है, जबकि कर्नेल वास्तविक I / करता है। ओ और जब तक यह पायथन प्रक्रिया को नहीं जगाता है जो मूल रूप से रुकावट भेजती है? सिद्धांत रूप में पायथन दुभाषिया कैसे हो सकता है, इस बारे में पता होना चाहिए?

I / O किसी भी अवरुद्ध कॉल है। Asyncio में, सभी I / O ऑपरेशंस को इवेंट लूप के माध्यम से जाना चाहिए, क्योंकि जैसा कि आपने कहा, ईवेंट लूप के पास यह पता करने का कोई तरीका नहीं है कि कुछ सिंक्रोनस कोड में एक ब्लॉकिंग कॉल किया जा रहा है। इसका मतलब है कि आप openएक coroutine के संदर्भ में सिंक्रोनस का उपयोग करने वाले नहीं हैं । इसके बजाय, एक समर्पित लाइब्रेरी का उपयोग करें जैसे aiofiles जो एक अतुल्यकालिक संस्करण प्रदान करता है open


यह yield fromकहते हुए कि कोरटाइन का उपयोग करके लागू किया जाता है, वास्तव में कुछ भी नहीं कहता है। yield fromयह केवल एक सिंटैक्स निर्माण है, यह एक मौलिक बिल्डिंग ब्लॉक नहीं है जिसे कंप्यूटर निष्पादित कर सकते हैं। इसी तरह, चुनिंदा लूप के लिए। हां, गो में कोरटाइन भी चुनिंदा लूप का उपयोग करते हैं, लेकिन मैं जो करने की कोशिश कर रहा था वह गो में काम करेगा, लेकिन पायथन में नहीं है। मुझे यह समझने के लिए अधिक विस्तृत उत्तरों की आवश्यकता है कि यह क्यों काम नहीं किया।
wvxvw

सॉरी ... नहीं, सच में नहीं। "भविष्य", "कार्य", "पारदर्शी तरीका", "से उपज" केवल buzzwords हैं, वे प्रोग्रामिंग के डोमेन से ऑब्जेक्ट नहीं हैं। प्रोग्रामिंग में चर, प्रक्रियाएं और संरचनाएं हैं। इसलिए, यह कहना कि "गोरोइन एक कार्य है" केवल एक गोलाकार कथन है जो एक प्रश्न पूछता है। अंत में, asyncioमेरे लिए क्या करता है, इसका स्पष्टीकरण , सी कोड को उबाल देगा जो दिखाता है कि पायथन सिंटैक्स का अनुवाद किया गया है।
wvxvw

आगे यह बताने के लिए कि आपका उत्तर मेरे प्रश्न का उत्तर क्यों नहीं देता है: आपके द्वारा दी गई सभी जानकारी के साथ, मुझे इस बात का कोई पता नहीं है कि लिंक किए गए प्रश्न में मेरे द्वारा पोस्ट किए गए कोड से मेरा प्रयास क्यों नहीं हुआ। मुझे पूरा यकीन है कि मैं इवेंट लूप को इस तरह से लिख सकता हूं कि यह कोड काम करेगा। वास्तव में, यह तरीका होगा कि मैं एक इवेंट लूप लिखूं, अगर मुझे एक लिखना था।
wvxvw

7
@wvxvw मैं असहमत हूं। वे "buzzwords" नहीं हैं बल्कि उच्च-स्तरीय अवधारणाएँ हैं जो कई पुस्तकालयों में लागू की गई हैं। उदाहरण के लिए, एक एसिंको टास्क, एक जेंटेन ग्रीनलेट और एक गोरोइन सभी एक ही चीज से मेल खाते हैं: एक निष्पादन इकाई जो एकल धागे के भीतर समवर्ती रूप से चल सकती है। इसके अलावा, मुझे नहीं लगता कि सी को एसिंको को समझने की आवश्यकता है, जब तक कि आप पायथ जनरेटर के आंतरिक कामकाज में नहीं आना चाहते।
विन्सेन्ट

@wvxvw मेरा दूसरा संपादन देखें इससे कुछ गलतफहमियां दूर होनी चाहिए।
विन्सेन्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.