अंदर से बाहर अजगर। प्रक्रिया संरचनाएं

1. परिचय
2. वस्तु। सिर
3. वस्तु। पूंछ
4. प्रक्रिया संरचनाएं

हम पायथन के इनसाइड पर लेखों की एक श्रृंखला का अनुवाद जारी रखते हैं। यदि आप कभी सोचें कि "यह कैसे व्यवस्थित है?", तो अवश्य पढ़ें। लेखक भाषा की संरचना के कई दिलचस्प और महत्वपूर्ण पहलुओं पर प्रकाश डालता है।

पिछले भागों में, हमने पायथन ऑब्जेक्ट सिस्टम के बारे में बात की। विषय अभी समाप्त नहीं हुआ है, लेकिन चलो आगे बढ़ते हैं।

जब मैं पायथन के कार्यान्वयन के बारे में सोचता हूं, तो मैं एक विशाल कन्वेयर की कल्पना करता हूं जिसके साथ मशीन कोड चलते हैं, जो फिर एक विशाल कारखाने में जाते हैं, जहां हर जगह टॉवर और टॉवर क्रेन उठते हैं - और मैं बस करीब आने की इच्छा से अभिभूत हूं। इस भाग में, हम दुभाषिया की स्थिति और धारा की स्थिति ( ./Python/pystate.c ) के बारे में बात करेंगे। अब हमें नींव रखने की आवश्यकता है ताकि बाद में यह समझना आसान हो जाए कि बायटेकोड को कैसे निष्पादित किया जाता है। जल्द ही हमें पता चलेगा कि फ्रेम, नेमस्पेस और कोड ऑब्जेक्ट कैसे व्यवस्थित होते हैं। लेकिन पहले, चलो उन डेटा संरचनाओं के बारे में बात करते हैं जो सब कुछ एक साथ बांधते हैं। ध्यान रखें, मैं मानता हूं कि ऑपरेटिंग सिस्टम की संरचना की कम से कम सतही समझ है और कर्नेल , प्रक्रिया , धागा आदि जैसे कम से कम ऐसे शब्दों का ज्ञान है।

कई ऑपरेटिंग सिस्टम में, उपयोगकर्ता कोड को थ्रेड्स में निष्पादित किया जाता है जो प्रक्रियाओं में रहते हैं (यह अधिकांश * निक्स सिस्टम और विंडोज के "आधुनिक" संस्करणों के लिए सच है)। कर्नेल प्रक्रियाओं और थ्रेड्स को तैयार करने और हटाने के लिए जिम्मेदार है, साथ ही यह निर्धारित करने के लिए कि किस थ्रेड पर तार्किक CPU निष्पादित किया जाएगा। जब एक प्रक्रिया Py_Initialize फ़ंक्शन को कॉल Py_Initialize है, तो एक और अमूर्त, दुभाषिया, दृश्य में प्रवेश करता है। कोई भी पायथन कोड जो एक प्रक्रिया में चलता है एक दुभाषिया से जुड़ा होता है। दुभाषिया को उन सभी अवधारणाओं के आधार के रूप में सोचा जा सकता है जिनके बारे में हम चर्चा करेंगे। पायथन एक प्रक्रिया में दो (या अधिक) दुभाषियों के आरंभ का समर्थन करता है। इस तथ्य के बावजूद कि इस सुविधा का उपयोग शायद ही कभी किया जाता है, मैं इसे ध्यान में रखूंगा। जैसा कि उल्लेख किया गया है, कोड को एक थ्रेड (या थ्रेड्स) में निष्पादित किया जाता है। कोई अपवाद नहीं है और पायथन वर्चुअल मशीन (वीएम)। इसके अलावा, VM के पास थ्रेड सपोर्ट है, अर्थात थ्रेड्स का प्रतिनिधित्व करने के लिए पायथन का अपना अमूर्तन है। इस अमूर्त का कार्यान्वयन पूरी तरह से कर्नेल तंत्र पर निर्भर करता है। इस प्रकार, कर्नेल और पायथन दोनों को पायथन थ्रेड्स का एक विचार है। ये थ्रेड कर्नेल द्वारा प्रबंधित होते हैं और सिस्टम में अन्य सभी थ्रेड्स के समानांतर अलग थ्रेड के रूप में चलते हैं। खैर ... लगभग समानांतर में।

अब तक, हमने अपनी चीन की दुकान में हाथी पर ध्यान नहीं दिया है। हाथी का नाम GIL ( ग्लोबल इंटरप्रेटर लॉक ) है। किसी कारण से, सीपीथॉन के कई पहलू थ्रेड सुरक्षित नहीं हैं। इसके दोनों फायदे हैं (उदाहरण के लिए, कई पायथन ऑपरेटरों के कार्यान्वयन और सरलीकृत कार्यान्वयन की गारंटी), और नुकसान। मुख्य नुकसान एक तंत्र की आवश्यकता है जो पायथन प्रवाह के समानांतर निष्पादन को रोकता है, क्योंकि इस तरह के तंत्र के बिना डेटा भ्रष्टाचार संभव है। GIL एक प्रोसेस लेवल लॉक है जिसे पायथन कोड को निष्पादित करने के लिए किसी थ्रेड को पकड़ना चाहिए। यह एक तार्किक CPU पर एक साथ पायथन थ्रेड को एक साथ चलाने की संख्या को सीमित करता है। अजगर धागे सहकारी मल्टीटास्किंग को लागू करते हैं, स्वेच्छा से जीआईएल जारी करते हैं और अन्य धागे को काम करने का अवसर देते हैं। यह कार्यक्षमता निष्पादन चक्र में निर्मित होती है, अर्थात आपको नियमित रूप से स्क्रिप्ट और कुछ एक्सटेंशन लिखते समय इस लॉक के बारे में विशेष रूप से सोचने की ज़रूरत नहीं है (ऐसा लगता है कि वे लगातार काम करते हैं)। ध्यान रखें कि जबकि एक धागा पायथन एपीआई का उपयोग नहीं करता है (यह कई के साथ होता है), यह अन्य पायथन थ्रेड्स के समानांतर काम कर सकता है। थोड़ी देर बाद हम जीआईएल पर चर्चा करेंगे, और जो लोग इंतजार नहीं कर सकते हैं वे डेविड बीस्ली की प्रस्तुति को पढ़ सकते हैं।

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

आइए अभिव्यक्ति spam = eggs - 1 ( क्या है ) द्वारा उत्पन्न बाइटकोड पर एक और नज़र डालते हैं:

 >>> diss("spam = eggs - 1") 1 0 LOAD_NAME 0 (eggs) 3 LOAD_CONST 0 (1) 6 BINARY_SUBTRACT 7 STORE_NAME 1 (spam) 10 LOAD_CONST 1 (None) 13 RETURN_VALUE >>> 

ऑपरेशन BINARY_SUBTRACT अलावा, जो सभी काम करता है, हम संचालन LOAD_NAME (eggs) और STORE_NAME (spam) । जाहिर है, इन ऑपरेशनों को करने के लिए, आपको एक जगह की आवश्यकता होती है: eggs को कहीं से बाहर निकालने की आवश्यकता होती है, और spam को कहीं दूर करने की आवश्यकता होती है। यह स्थान आंतरिक डेटा संरचनाओं द्वारा संदर्भित किया जाता है जिसमें कोड निष्पादित होता है - फ्रेम ऑब्जेक्ट और कोड ऑब्जेक्ट । जब आप पायथन कोड चलाते हैं, तो फ़्रेम वास्तव में निष्पादित होते हैं (याद रखें ceval.c : PyEval_EvalFrameEx )। अब हम फ्रेम ऑब्जेक्ट और कोड ऑब्जेक्ट की अवधारणाओं को मिला रहे हैं, इसलिए अभी के लिए यह आसान है। हम थोड़ी देर बाद इन संरचनाओं के बीच अंतर को समझेंगे। अब हम फ्रेम ऑब्जेक्ट के f_back क्षेत्र में सबसे अधिक रुचि रखते हैं। फ्रेम n यह फ़ील्ड फ्रेम n-1 , यानी की ओर इशारा करती है। उस फ़्रेम को जिसके कारण वर्तमान फ़्रेम (स्ट्रीम में पहला फ़्रेम NULL इंगित करता NULL )।

फ़्रेम स्टैक प्रत्येक थ्रेड के लिए अद्वितीय है और थ्रेड-विशिष्ट संरचना के साथ जुड़ा हुआ है ./Include.h/pystate.h : PyThreadState , जिसमें स्ट्रीम के वर्तमान निष्पादन योग्य फ्रेम (सबसे अधिक जिसे फ्रेम कहा जाता है, स्टैक के शीर्ष) का सूचक है। PyThreadState संरचना PyThreadState प्रत्येक Python थ्रेड के लिए आवंटित किया गया है और इस प्रक्रिया में _PyThreadState_Prealloc फ़ंक्शन द्वारा _PyThreadState_Prealloc किया गया है, OS से अनुरोध किए जाने से ठीक पहले OS (( ./Modules/_threadmodule.c : thread_PyThread_start_new_thread और thread_PyThread_start_new_thread >>> from _thread import start_new_thread । इस प्रक्रिया में, ऐसे धागे भी बनाए जा सकते हैं जो दुभाषिया द्वारा नियंत्रित नहीं होते हैं; उनके पास PyThreadState संरचना नहीं है, और उन्हें पायथन एपीआई का उपयोग नहीं करना चाहिए। यह ज्यादातर एम्बेडेड अनुप्रयोगों में होता है। लेकिन इस तरह के धागे "pythonized" हो सकते हैं ताकि उनमें पायथन कोड निष्पादित करना संभव हो जाए, और आपको एक नया PyThreadState संरचना बनाने की आवश्यकता है। यदि एक दुभाषिया चल रहा है, तो आप ऐसे स्ट्रीम माइग्रेशन के लिए एपीआई का उपयोग कर सकते हैं। यदि कई व्याख्याकार हैं, तो आपको इसे मैन्युअल रूप से करना होगा। अंत में, उसी तरह से कि प्रत्येक फ्रेम पिछले एक से एक पॉइंटर के माध्यम से जुड़ा हुआ है, थ्रेड्स के राज्यों को पॉइंटर्स PyThreadState *next की एक लिंक्ड सूची द्वारा संयुक्त किया जाता है।

स्ट्रीम संरचनाओं की सूची इंटरप्रेटर की संरचना से जुड़ी होती है जिसमें धाराएं स्थित होती हैं। दुभाषिया संरचना में परिभाषित किया गया है ./Include.h/pystate.h : PyInterpreterState । यह तब बनाया जाता है जब Py_Initialize फ़ंक्शन को Py_Initialize , जो प्रक्रिया में Python वर्चुअल मशीन को आरंभ करता है, या जब Py_NewInterpreter फ़ंक्शन को Py_NewInterpreter , जिसमें एक नया दुभाषिया संरचना बनाई जाती है (यदि प्रक्रिया में एक से अधिक दुभाषिया हो)। एक बेहतर समझ के लिए, मुझे याद है कि Py_NewInterpreter इंटरप्रेटर संरचना को वापस नहीं करता है, लेकिन नए इंटरप्रेटर के लिए नए बनाए गए थ्रेड का PyThreadState संरचना। इसमें एक भी धागे के बिना एक नया दुभाषिया बनाना बहुत मायने नहीं रखता है, जैसे कि यह उन में बिना धागे के प्रक्रियाओं में कोई मतलब नहीं रखता है। प्रक्रिया में दुभाषियों की संरचना उसी तरह से एक दूसरे से संबंधित होती है जिस तरह दुभाषिया में प्रवाह की संरचनाएं होती हैं।

सामान्य तौर पर, एक एकल ऑपरेशन से एक पूरी प्रक्रिया तक हमारी यात्रा पूरी हो जाती है: संचालन निष्पादन योग्य कोड ऑब्जेक्ट्स में होते हैं (जबकि "गैर-निष्पादित" ऑब्जेक्ट कहीं पास होते हैं, नियमित डेटा की तरह), कोड ऑब्जेक्ट निष्पादन योग्य फ़्रेम में होते हैं जो पायथन में होते हैं -स्ट्रीम, और स्ट्रीम, बदले में, दुभाषिया के हैं। इस संरचना की जड़ को स्थिर चर द्वारा संदर्भित किया जाता है ./Python/pystate.c : interp_head । यह पहले दुभाषिया की संरचना को इंगित करता है (इसके माध्यम से अन्य सभी दुभाषियों, धाराओं, आदि उपलब्ध हैं)। head_mutex mutex विभिन्न थ्रेड्स से परिवर्तन की प्रतिस्पर्धा करके इन संरचनाओं को नुकसान से बचाता है (मैं निर्दिष्ट करता हूं कि यह GIL नहीं है, लेकिन दुभाषिया और थ्रेड संरचनाओं के लिए एक नियमित म्यूटेक्स है)। इस लॉक को HEAD_LOCK और HEAD_UNLOCK द्वारा नियंत्रित किया जाता है। एक नियम के रूप में, यदि नया जोड़ना या किसी मौजूदा दुभाषिया या स्ट्रीम को हटाना आवश्यक interp_head , तो interp_head चर एक्सेस किया जाता है। यदि प्रक्रिया में एक से अधिक दुभाषिया है, तो जरूरी नहीं कि दुभाषिया जिसमें वर्तमान में निष्पादित धारा है, की संरचना इस चर द्वारा उपलब्ध है।

यह चर का उपयोग करने के लिए सुरक्षित है ./Python/pystate.c : _PyThreadState_Current , जो निष्पादन योग्य थ्रेड की संरचना को इंगित करता है (कुछ शर्तों को ध्यान में रखा जाना चाहिए)। यही है, अपने दुभाषिया को प्राप्त करने के लिए, कोड को अपनी धारा की संरचना की आवश्यकता होती है, जिसमें से दुभाषिया को पहले ही बाहर निकाला जा सकता है। इस चर का उपयोग करने के लिए (वर्तमान मूल्य को लें या इसे बदल दें, पुराने को रखते हुए) ऐसे कार्य हैं जिनके लिए आपको जीआईएल को रखने की आवश्यकता है। यह महत्वपूर्ण है, और यह उन समस्याओं में से एक है जो सीपीथॉन में धागा सुरक्षा की कमी के कारण उत्पन्न होती हैं। चर _PyThreadState_Current मान पायथन इनिशियलाइज़ेशन के दौरान या नए थ्रेड के निर्माण के दौरान नए थ्रेड की संरचना में सेट किया गया है। जब पायथन थ्रेड बूट के बाद पहली बार शुरू होता है, तो यह निम्न पर निर्भर करता है: ए) यह जीआईएल रखता है और बी) _PyThreadState_Current चर _PyThreadState_Current मान सही है। इस बिंदु पर, सूत्र को जीआईएल नहीं देना चाहिए। सबसे पहले, इसे कहीं न कहीं मूल्य को बचाना होगा _PyThreadState_Current , ताकि अगली बार जब आप GIL पर कब्जा करें, तो आप चर के वांछित मूल्य को पुनर्स्थापित कर सकें और काम करना जारी रख सकें। इस व्यवहार के लिए, _PyThreadState_Current वर्तमान में निष्पादित थ्रेड को इंगित करता है। इस व्यवहार को लागू करने के लिए मैक्रोज़ Py_BEGIN_ALLOW_THREADS और Py_END_ALLOW_THREADS हैं। आप GIL और API के बारे में घंटों तक काम करने के बारे में बात कर सकते हैं, और CPython की तुलना अन्य कार्यान्वयन (उदाहरण के लिए, Jython या IronPython, जिसमें थ्रेड एक साथ निष्पादित होते हैं) के साथ तुलना करना दिलचस्प होगा। लेकिन अभी के लिए इस विषय को स्थगित करते हैं।

आरेख में, मैंने एक प्रक्रिया की संरचनाओं के बीच कनेक्शन का संकेत दिया, जिसमें दो दुभाषियों को दो थ्रेड्स के साथ लॉन्च किया गया है, जो फ्रेम के उनके ढेर को संदर्भित करता है।
छवि

अच्छा लगा ना? So. उन्होंने सब कुछ पर चर्चा की, लेकिन यह अभी भी स्पष्ट नहीं है कि इन संरचनाओं का अर्थ क्या है। उनकी आवश्यकता क्यों है? उनके बारे में क्या दिलचस्प है? मैं उलझना नहीं चाहता, इसलिए केवल कुछ कार्यों के बारे में संक्षेप में बात करता हूं। उदाहरण के लिए, दुभाषिया की संरचना में, आयातित मॉड्यूल के साथ काम करने के लिए डिज़ाइन किए गए फ़ील्ड हैं; यूनिकोड के साथ काम करने के लिए आवश्यक संकेत; डायनामिक लिंकर फ़्लैग फ़ील्ड और प्रोफ़ाइलिंग के लिए TSC का उपयोग करने से संबंधित फ़ील्ड (यहां का पारंगत पैराग्राफ देखें)।

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

मुझे लगता है कि यह वह जगह है जहां पायथन प्रक्रिया की संरचना के बारे में कहानी पूरी हो सकती है। मुझे उम्मीद है कि सब कुछ स्पष्ट है। निम्नलिखित पोस्ट में, हम वास्तविक कट्टर पर आगे बढ़ेंगे और फ्रेम ऑब्जेक्ट , नेमस्पेस, और कोड ऑब्जेक्ट के बारे में बात करेंगे। तैयार हो जाओ।

Source: https://habr.com/ru/post/In191032/


All Articles