प्रॉम्प्ट और संदर्भ
एक बैच प्रोसेसर कई ऑब्जेक्ट्स को समवर्ती (concurrently) रूप से संभालता है। पैरेंट goroutine सभी टास्क के समाप्त होने की प्रतीक्षा करता है और जब कोई टास्क विफल हो जाता है या कॉन्टेक्स्ट रद्द हो जाता है तो डिस्पैचिंग बंद कर देता है। लाइफसाइकिल को परिभाषित करने के लिए Go 1.25 sync.WaitGroup.Go का उपयोग करें, और Add, Done, तथा Wait के साथ इसके संबंध, इसके पैनिक कॉन्ट्रैक्ट, एरर प्रोपेगेशन और कैंसलेशन सीमाओं की व्याख्या करें। यह एक coding प्रश्न है क्योंकि मुख्य कौशल कॉन्करेंट टास्क लाइफसाइकिल डिज़ाइन है।
इंटरव्यूअर क्या मूल्यांकन करता है
पहला, क्या आप Add को ऐसी जगह रखने के बजाय जहाँ यह Wait के साथ रेस कर सकता है, टास्क निर्माण को काउंटिंग से बाँधते हैं।
दूसरा, क्या आप WaitGroup.Go कॉन्ट्रैक्ट को समझते हैं: फ़ंक्शन को पैनिक नहीं करना चाहिए, और इसके वापस आने पर काउंटर घट जाता है; कोई एरर या कैंसलेशन प्रोपेगेशन प्रदान नहीं किया जाता है।
तीसरा, क्या आप बिना किसी लीक, रेस या असीमित कतारों के बाउंडेड कॉन्करेंसी डिज़ाइन कर सकते हैं, डिस्पैचिंग रोक सकते हैं और परिणाम एकत्र कर सकते हैं।
चौथा, क्या आप पहचान सकते हैं कि WaitGroup को एक पूर्ण ऑर्केस्ट्रेशन प्रिमिटिव मानने की तुलना में errgroup.WithContext कब अधिक उपयुक्त है।
पाँचवाँ, क्या परीक्षण Go संस्करण और CI कॉन्ट्रैक्ट को स्पष्ट रखते हुए सफलता, कैंसलेशन, पैनिक सुरक्षा और वेट रेस को कवर करते हैं।
पहले स्पष्ट करने योग्य प्रश्न
- क्या कंपाइलर Go 1.25 या बाद के संस्करण पर पिन किया गया है?
- कॉन्करेंसी सीमा क्या है, और क्या इनपुट एक अनबाउंडेड स्ट्रीम हो सकता है?
- क्या पहली त्रुटि (first error) को सिबलिंग टास्क को रद्द करना चाहिए, या सभी त्रुटियों को एकत्र किया जाना चाहिए?
- क्या कोई टास्क पैनिक कर सकता है, और यदि नहीं, तो कौन सी लेयर इसे रिकवर करती है?
- क्या परिणामों को इनपुट क्रम सुरक्षित रखना चाहिए, और क्या त्रुटियों में ऑब्जेक्ट आइडेंटिफ़ायर की आवश्यकता है?
- क्या डाउनस्ट्रीम कॉल कॉन्टेक्स्ट कैंसलेशन और आइडेम्पोटेंट पुनः प्रयासों (retries) का समर्थन करते हैं?
30 सेकंड का उत्तर
“मैं प्रत्येक लॉन्च को उसके काउंटर से बाँधने के लिए WaitGroup.Go और कॉन्करेंसी को सीमित करने के लिए एक सेमाफोर का उपयोग करता हूँ। प्रत्येक टास्क कॉन्टेक्स्ट की जाँच करता है और एक सुरक्षित कलेक्टर के माध्यम से परिणाम या त्रुटियाँ लिखता है। WaitGroup केवल प्रतीक्षा करता है; यह त्रुटियों या कैंसलेशन का प्रसार नहीं करता है, इसलिए पहली त्रुटि को स्पष्ट रूप से एक व्युत्पन्न (derived) कॉन्टेक्स्ट को रद्द करना चाहिए। यदि पहली-त्रुटि पर कैंसलेशन मानक नीति है, तो मैं errgroup.WithContext का उपयोग करूँगा। टास्क एंट्री पॉइंट को या तो अनुमत पैनिक को एरर में रिकवर करना चाहिए या प्रोसेस-लेवल नीति को स्पष्ट करना चाहिए। परीक्षण कैंसलेशन, पीक कॉन्करेंसी, कन्वर्जेंस और रेस को कवर करते हैं।”
विस्तृत समाधान
चरण 1: API कॉन्ट्रैक्ट को पिन करें
Go 1.25 ने sync.WaitGroup में Go(f func()) जोड़ा है। यह f शुरू करता है और f के वापस आने पर Done के समकक्ष कार्य करता है; दस्तावेज़ीकरण की आवश्यकता है कि f पैनिक न करे। go.mod, CI इमेज और स्थानीय टूलींग में संस्करण को पिन करें ताकि पुराने कंपाइलर चुपचाप वर्कफ़्लो में प्रवेश न कर सकें।
चरण 2: टास्क सीमा पर कॉन्करेंसी सीमा लागू करें
Go को कॉल करने से पहले या टास्क के अंदर एक बफ़र्ड सेमाफोर का उपयोग करें। यदि डिस्पैच ब्लॉक हो सकता है, तो अधिग्रहण (acquisition) को रद्द करने योग्य बनाएं ताकि एक रद्द किया गया कॉन्टेक्स्ट डिस्पैचर को हमेशा के लिए प्रतीक्षा में न छोड़ दे।
var wg sync.WaitGroup
sem := make(chan struct{}, 8)
for _, item := range items {
if err := ctx.Err(); err != nil { break }
sem <- struct{}{}
item := item
wg.Go(func() {
defer func() { <-sem }()
if ctx.Err() != nil { return }
process(item)
})
}
wg.Wait()चरण 3: एरर और कैंसलेशन चैनल परिभाषित करें
WaitGroup कोई एरर स्टोर नहीं करता है और सिबलिंग टास्क को रद्द नहीं करता है। एक रद्द करने योग्य कॉन्टेक्स्ट प्राप्त करें; त्रुटि रिकॉर्ड करने वाला पहला टास्क कैंसल को कॉल करता है। कलेक्टर के लिए एक चैनल या म्यूटेक्स का उपयोग करें, और समवर्ती एक्सेस से बचने के लिए Wait के वापस आने के बाद इसे पढ़ें।
चरण 4: पैनिक सीमा को स्पष्ट बनाएं
यदि सर्विस पैनिक को एक रिकवरेबल टास्क विफलता मानती है, तो एक डेफ़र्ड recover इसे स्टैक ट्रेस के साथ एक एरर में बदल सकता है और कैंसलेशन ट्रिगर कर सकता है। यदि पैनिक एक टूटे हुए इनवेरिएंट को इंगित करता है, तो चुपचाप रिकवर न करें; लॉगिंग, अलर्टिंग और प्रोसेस-एग्जिट व्यवहार का दस्तावेजीकरण करें।
चरण 5: डिस्पैच-एंड-वेट रेस से बचें
जब तक कोई लाइफसाइकिल प्रोटोकॉल इसे सुरक्षित न बनाए, तब तक Wait को कॉल न करें जब तक कि कोई अन्य goroutine अभी भी Go को कॉल कर सकता है। एक बैच प्रोसेसर में एक डिस्पैचर होना चाहिए जो यह तय करे कि कब कोई और टास्क नहीं जोड़ा जाएगा, फिर Wait को कॉल करे। डायनेमिक रिकर्सिव टास्क को यह बताना चाहिए कि नेस्टेड Go कॉल की अनुमति कब है।
चरण 6: errgroup से तुलना करें
errgroup.WithContext पहली गैर-शून्य एरर, व्युत्पन्न कैंसलेशन और एक वैकल्पिक सीमा प्रदान करता है, इसलिए यह रिक्वेस्ट फ़ैन-आउट के लिए उपयुक्त है जहाँ एक विफलता अन्य सिबलिंग को रोक देती है। WaitGroup.Go स्वतंत्र टास्क, कस्टम एरर एकत्रीकरण, या किसी अन्य घटक द्वारा प्रबंधित लाइफसाइकिल के लिए उपयुक्त है।
चरण 7: इनवेरिएंट्स को सत्यापित करें
परीक्षणों को यह साबित करना चाहिए कि प्रत्येक लॉन्च किया गया टास्क अंततः काउंट को घटाता है; कैंसलेशन नए डिस्पैच को रोकता है; एक एरर एक बार कैंसलेशन ट्रिगर करती है; पीक सीमा के भीतर रहता है; और Wait के बाद परिणामों में बदलाव बंद हो जाता है। कलेक्टर रेस का पता लगाने के लिए go test -race चलाएं।
एक उच्च-गुणवत्ता वाला नमूना उत्तर
“मैं पहले Go 1.25 को पिन करता हूँ। डिस्पैचर कॉन्टेक्स्ट के सक्रिय रहने के दौरान एक सेमाफोर प्राप्त करता है, फिर wg.Go को कॉल करता है; टास्क सेमाफोर को रिलीज़ करता है, कैंसलेशन की जाँच करता है और कार्य निष्पादित करता है। WaitGroup केवल लाइफसाइकिल प्रतीक्षा प्रदान करता है, इसलिए मैं पहली-त्रुटि नीति के लिए एक व्युत्पन्न कॉन्टेक्स्ट, ऑब्जेक्ट ID ले जाने वाला एक एरर चैनल और वन-शॉट कैंसलेशन जोड़ता हूँ। यदि पैनिक रिकवरेबल है, तो मैं इसे इसके स्टैक के साथ एक एरर में बदल देता हूँ; अन्यथा प्रोसेस-लेवल हैंडलिंग स्पष्ट रहती है। मैं Wait से पहले डिस्पैचिंग रोकता हूँ, बाद में परिणाम एकत्रित करता हूँ, और go test -race चलाता हूँ। पहली-त्रुटि कैंसलेशन और एक सीमा के लिए, मैं errgroup.WithContext चुनूँगा।”
सामान्य गलतियाँ
WaitGroup.Goको एक error group मानना → त्रुटियाँ प्रसारित नहीं होती हैं → उन्हें स्पष्ट रूप से एकत्र करें या errgroup का उपयोग करें।- कैंसलेशन के बाद सेमाफोर पर ब्लॉक होना → डिस्पैचर बाहर नहीं निकल सकता → अधिग्रहण के दौरान कॉन्टेक्स्ट पर select का उपयोग करें।
- टास्क को सीधे पैनिक करने देना → यह कॉन्ट्रैक्ट का उल्लंघन करता है और प्रोसेस को निरस्त कर सकता है → अनुमत पैनिक को परिवर्तित करें या एक स्पष्ट प्रोसेस नीति का उपयोग करें।
- कई goroutines से एक ही स्लाइस में append करना → डेटा रेस उत्पन्न होती है → एक चैनल, म्यूटेक्स या पोस्ट-वेट एकत्रीकरण का उपयोग करें।
- डिस्पैच समाप्त होने से पहले Wait को कॉल करना → गतिशील जोड़ प्रतीक्षा के साथ रेस करते हैं → एक स्टॉप-डिस्पैच प्रोटोकॉल परिभाषित करें।
- केवल अंतिम काउंट को सत्यापित करना → कैंसलेशन और पीक कॉन्करेंसी बग छिप जाते हैं → प्रत्येक इनवेरिएंट को असर्ट करें और रेस डिटेक्टर चलाएं।
- Go संस्करण की उपेक्षा करना → स्थानीय और CI व्यवहार अलग हो जाते हैं → मॉड्यूल, इमेज और टूलचेन को पिन करें।
- प्रत्येक ऑर्केस्ट्रेशन चिंता के लिए WaitGroup का उपयोग करना → पुनः प्रयास, समय सीमा और पहली-त्रुटि नीति बिखर जाती है → उपयुक्त होने पर errgroup या एक समर्पित शेड्यूलर चुनें।
फ़ॉलो-अप प्रश्न
फ़ॉलो-अप 1: क्या Go ऐसा फ़ंक्शन प्राप्त कर सकता है जो पैनिक करता है?
आधिकारिक दस्तावेज़ के अनुसार फ़ंक्शन को पैनिक नहीं करना चाहिए। यदि पैनिक एक रिकवरेबल बिज़नेस विफलता है, तो एक परिभाषित सीमा पर रिकवर करें और एक एरर लौटाएं; अन्यथा पैनिक सेमांटिक्स को बनाए रखें और प्रोसेस-लेवल रिकवरी और अलर्टिंग नीति पर भरोसा करें।
फ़ॉलो-अप 2: आप यह कैसे गारंटी देते हैं कि कैंसलेशन के बाद कोई टास्क शुरू नहीं होगा?
डिस्पैच से पहले ctx.Err() की जाँच करें, सेमाफोर प्राप्त करते समय एक रद्द करने योग्य select का उपयोग करें, और टास्क प्रविष्टि पर फिर से कॉन्टेक्स्ट की जाँच करें। पहले से चल रहा कार्य अभी भी डाउनस्ट्रीम API द्वारा कैंसलेशन का सम्मान करने पर निर्भर करता है।
फ़ॉलो-अप 3: errgroup कब बेहतर है?
पहली-त्रुटि प्रसार, सिबलिंग कैंसलेशन और एकीकृत प्रतीक्षा के लिए errgroup.WithContext चुनें। स्वतंत्र टास्क या कस्टम एरर एकत्रीकरण के लिए WaitGroup.Go चुनें।
फ़ॉलो-अप 4: क्या प्रतीक्षा करते समय Go को कॉल किया जा सकता है?
केवल एक ऐसे लाइफसाइकिल प्रोटोकॉल के साथ जो नया कार्य जोड़े जाने से पहले काउंटर को शून्य तक पहुँचने से रोकता है। शून्य-गणना वाली रेस से बचने के लिए एक सामान्य बैच को Wait से पहले डिस्पैचिंग बंद कर देनी चाहिए।
फ़ॉलो-अप 5: आप परिणाम के क्रम को कैसे सुरक्षित रखते हैं?
प्रत्येक इनपुट को एक इंडेक्स असाइन करें और किसी टास्क को उसके स्लॉट में लिखने दें या एक इंडेक्स किया गया परिणाम भेजने दें। प्रतीक्षा के बाद इंडेक्स द्वारा एकत्र करें; टास्क के बीच अपेंड-ओनली स्लाइस साझा न करें।
फ़ॉलो-अप 6: आप पैनिक रिकवरी का परीक्षण कैसे करते हैं?
एक पैनिकिंग टास्क इंजेक्ट करें और दावा (assert) करें कि रिकवरी लेयर एक पहचानी गई त्रुटि उत्सर्जित करती है, कैंसलेशन ट्रिगर करती है, और अन्य टास्क को समाप्त (converge) होने देती है। सहमत निगरानी और प्रोसेस नीति के विरुद्ध गैर-रिकवरी पथ का अलग से परीक्षण करें।