यह पोस्ट का एक अंश है; पूरी पोस्ट यहां उपलब्ध है: गोलांग डेफ़र: बेसिक से ट्रैप तक।
जब हम गो सीखना शुरू करते हैं तो डिफर स्टेटमेंट शायद पहली चीजों में से एक है जो हमें काफी दिलचस्प लगती है, है ना?
लेकिन इसमें और भी बहुत कुछ है जो कई लोगों को परेशान करता है, और कई दिलचस्प पहलू हैं जिन्हें हम अक्सर इसका उपयोग करते समय नहीं छूते हैं।
उदाहरण के लिए, डिफर स्टेटमेंट वास्तव में 3 प्रकार के होते हैं (गो 1.22 के अनुसार, हालांकि यह बाद में बदल सकता है): ओपन-कोडेड डिफर, हीप-आवंटित डिफर, और स्टैक-आवंटित। प्रत्येक का अलग-अलग प्रदर्शन और अलग-अलग परिदृश्य होते हैं जहां उनका सबसे अच्छा उपयोग किया जाता है, यदि आप प्रदर्शन को अनुकूलित करना चाहते हैं तो यह जानना अच्छा है।
इस चर्चा में, हम बुनियादी बातों से लेकर अधिक उन्नत उपयोग तक सब कुछ कवर करने जा रहे हैं, और हम कुछ आंतरिक विवरणों में भी थोड़ा सा, बस थोड़ा सा खोदेंगे।
आइए बहुत गहराई में जाने से पहले डिफ़र पर एक नज़र डालें।
गो में, defer एक कीवर्ड है जिसका उपयोग किसी फ़ंक्शन के निष्पादन को तब तक विलंबित करने के लिए किया जाता है जब तक कि आसपास का फ़ंक्शन समाप्त न हो जाए।
func main() { defer fmt.Println("hello") fmt.Println("world") } // Output: // world // hello
इस स्निपेट में, defer स्टेटमेंट fmt.Println("hello") को मुख्य फ़ंक्शन के बिल्कुल अंत में निष्पादित करने के लिए शेड्यूल करता है। तो, fmt.Println("world") को तुरंत कॉल किया जाता है, और "world" पहले प्रिंट किया जाता है। उसके बाद, क्योंकि हमने डेफ़र का उपयोग किया था, "हैलो" मुख्य समापन से पहले अंतिम चरण के रूप में मुद्रित होता है।
यह किसी कार्य को बाद में चलाने के लिए सेट करने जैसा है, फ़ंक्शन समाप्त होने से ठीक पहले। यह सफ़ाई कार्यों के लिए वास्तव में उपयोगी है, जैसे डेटाबेस कनेक्शन बंद करना, म्यूटेक्स को मुक्त करना, या फ़ाइल बंद करना:
func doSomething() error { f, err := os.Open("phuong-secrets.txt") if err != nil { return err } defer f.Close() // ... }
उपरोक्त कोड यह दिखाने के लिए एक अच्छा उदाहरण है कि defer कैसे काम करता है, लेकिन यह defer का उपयोग करने का एक बुरा तरीका भी है। हम अगले भाग में उस पर विचार करेंगे।
"ठीक है, अच्छा है, लेकिन अंत में f.Close() क्यों नहीं लगाते?"
इसके कुछ अच्छे कारण हैं:
जब कोई घबराहट होती है, तो स्टैक खोल दिया जाता है और स्थगित कार्यों को एक विशिष्ट क्रम में निष्पादित किया जाता है, जिसे हम अगले भाग में कवर करेंगे।
जब आप किसी फ़ंक्शन में एकाधिक डिफर स्टेटमेंट का उपयोग करते हैं, तो उन्हें 'स्टैक' क्रम में निष्पादित किया जाता है, जिसका अर्थ है कि अंतिम स्थगित फ़ंक्शन पहले निष्पादित किया जाता है।
func main() { defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) } // Output: // 3 // 2 // 1
हर बार जब आप डिफ़र स्टेटमेंट को कॉल करते हैं, तो आप उस फ़ंक्शन को वर्तमान गोरोइन की लिंक की गई सूची के शीर्ष पर जोड़ रहे हैं, इस तरह:
और जब फ़ंक्शन वापस आता है, तो यह लिंक की गई सूची से गुजरता है और ऊपर की छवि में दिखाए गए क्रम में प्रत्येक को निष्पादित करता है।
लेकिन याद रखें, यह गोरोइन की लिंक की गई सूची में सभी डिफर को निष्पादित नहीं करता है, यह केवल लौटाए गए फ़ंक्शन में डिफर को चलाता है, क्योंकि हमारी डिफर लिंक की गई सूची में कई अलग-अलग कार्यों से कई डिफर शामिल हो सकते हैं।
func B() { defer fmt.Println(1) defer fmt.Println(2) A() } func A() { defer fmt.Println(3) defer fmt.Println(4) }
इसलिए, वर्तमान फ़ंक्शन (या वर्तमान स्टैक फ़्रेम) में केवल स्थगित फ़ंक्शन निष्पादित किए जाते हैं।
लेकिन एक विशिष्ट मामला है जहां वर्तमान गोरोइन में सभी स्थगित कार्यों का पता लगाया जाता है और निष्पादित किया जाता है, और तभी घबराहट होती है।
संकलन-समय त्रुटियों के अलावा, हमारे पास रनटाइम त्रुटियों का एक समूह है: शून्य से विभाजित करना (केवल पूर्णांक), सीमा से बाहर, शून्य सूचक को डीरेफ़र करना, इत्यादि। इन त्रुटियों के कारण एप्लिकेशन घबरा जाता है।
पैनिक वर्तमान गोरोइन के निष्पादन को रोकने, स्टैक को खोलने और वर्तमान गोरोइन में स्थगित कार्यों को निष्पादित करने का एक तरीका है, जिससे हमारा एप्लिकेशन क्रैश हो जाता है।
अप्रत्याशित त्रुटियों को संभालने और एप्लिकेशन को क्रैश होने से रोकने के लिए, आप घबराए हुए गोरोइन पर नियंत्रण पाने के लिए स्थगित फ़ंक्शन के भीतर पुनर्प्राप्ति फ़ंक्शन का उपयोग कर सकते हैं।
func main() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() panic("This is a panic") } // Output: // Recovered: This is a panic
आम तौर पर, लोग घबराहट में कोई त्रुटि डाल देते हैं और उसे पुनर्प्राप्त (..) के साथ पकड़ लेते हैं, लेकिन यह कुछ भी हो सकता है: एक स्ट्रिंग, एक इंट, आदि।
उपरोक्त उदाहरण में, स्थगित फ़ंक्शन के अंदर ही एकमात्र स्थान है जहां आप पुनर्प्राप्ति का उपयोग कर सकते हैं। मैं इसे थोड़ा और समझाता हूं।
कुछ गलतियाँ हैं जिन्हें हम यहां सूचीबद्ध कर सकते हैं। मैंने वास्तविक कोड में इस तरह के कम से कम तीन स्निपेट देखे हैं।
पहला है, पुनर्प्राप्ति को सीधे विलंबित फ़ंक्शन के रूप में उपयोग करना:
func main() { defer recover() panic("This is a panic") }
उपरोक्त कोड अभी भी घबराता है, और यह गो रनटाइम के डिज़ाइन के अनुसार है।
पुनर्प्राप्ति फ़ंक्शन घबराहट को पकड़ने के लिए है, लेकिन इसे ठीक से काम करने के लिए स्थगित फ़ंक्शन के भीतर बुलाया जाना चाहिए।
पर्दे के पीछे, पुनर्प्राप्त करने के लिए हमारी कॉल वास्तव में runtime.gorecover है, और यह जांचता है कि पुनर्प्राप्ति कॉल सही संदर्भ में हो रही है, विशेष रूप से सही स्थगित फ़ंक्शन से जो घबराहट होने पर सक्रिय थी।
"क्या इसका मतलब यह है कि हम इस तरह किसी स्थगित फ़ंक्शन के अंदर किसी फ़ंक्शन में पुनर्प्राप्ति का उपयोग नहीं कर सकते?"
func myRecover() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } } func main() { defer func() { myRecover() // ... }() panic("This is a panic") }
बिल्कुल, उपरोक्त कोड आपकी अपेक्षा के अनुरूप काम नहीं करेगा। ऐसा इसलिए है क्योंकि पुनर्प्राप्ति को सीधे किसी स्थगित फ़ंक्शन से नहीं बल्कि नेस्टेड फ़ंक्शन से कॉल किया जाता है।
अब, एक और गलती एक अलग गोरोइन से घबराहट को पकड़ने की कोशिश कर रही है:
func main() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() go panic("This is a panic") time.Sleep(1 * time.Second) // Wait for the goroutine to finish }
समझ में आता है, है ना? हम पहले से ही जानते हैं कि डिफर चेन एक विशिष्ट गोरोइन से संबंधित हैं। यह कठिन होगा यदि एक गोरोइनटाइन घबराहट को संभालने के लिए दूसरे में हस्तक्षेप कर सके क्योंकि प्रत्येक गोरोइनटाइन का अपना स्टैक होता है।
दुर्भाग्य से, इस मामले में एकमात्र रास्ता एप्लिकेशन को क्रैश करना है यदि हम उस गोरोइन में घबराहट को नहीं संभालते हैं।
मैं पहले भी इस समस्या का सामना कर चुका हूं, जहां पुराना डेटा एनालिटिक्स सिस्टम में चला गया था, और इसका कारण पता लगाना कठिन था।
मेरा मतलब यह है:
func pushAnalytic(a int) { fmt.Println(a) } func main() { a := 10 defer pushAnalytic(a) a = 20 }
आपको क्या लगता है आउटपुट क्या होगा? यह 10 है, 20 नहीं।
ऐसा इसलिए है क्योंकि जब आप defer स्टेटमेंट का उपयोग करते हैं, तो यह तुरंत मान पकड़ लेता है। इसे "मूल्य द्वारा कब्जा" कहा जाता है। इसलिए, पुशएनालिटिक को भेजे जाने वाले का मान 10 पर सेट होता है जब डिफर शेड्यूल किया जाता है, भले ही बाद में परिवर्तन होता है।
इसे ठीक करने के दो तरीके हैं।
...
पूरा पोस्ट यहां उपलब्ध है: गोलांग डिफर: फ्रॉम बेसिक टू ट्रैप।
अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।
Copyright© 2022 湘ICP备2022001581号-3