प्रतिनिधि इंटरव्यू विषय

आप Swift 6.2 की अप्रोचेबल कॉनकconcurrent (approachable concurrency) में कैसे माइग्रेट करते हैं?

कोडिंगकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

आपके पास Swift 5.10 के लिए लिखा गया एक इमेज-प्रोसेसिंग मॉड्यूल है। टीम UI को रिस्पॉन्सिव रखते हुए Swift 6.2 में सख्त कॉनकरेंसी चेक्स को धीरे-धीरे सक्षम करना चाहती है। आप आइसोलेशन सीमाएं कैसे निर्धारित करते हैं, @concurrent कैसे चुनते हैं, और यह कैसे साबित करते हैं कि माइग्रेशन डेटा रेस और अनपेक्षित सीरियलाइज़ेशन से बचाता है?

1. परिदृश्य और बाधाएं

आप एक इमेज-एडिटिंग ऐप का रखरखाव करते हैं। UI एक कैश तक पहुंचता है जबकि नेटवर्क फ़ेच, डिकोडिंग और थंबनेल निर्माण महंगे ऑपरेशन हैं। पुराना कोड अंतर्निहित थ्रेड हॉप्स पर निर्भर करता है, कुछ APIs nonisolated async हैं, और परीक्षण केवल अंतिम छवि की जांच करते हैं। आपको Swift 6.2 को अपनाना होगा, स्मूथ स्क्रॉलिंग और नियतात्मक (deterministic) परिणामों को बनाए रखना होगा, और Swift 6 डेटा-रेस चेकिंग को उत्तरोत्तर सक्षम करना होगा।

आधार रेखा (baselines) दर्ज करके शुरुआत करें: मुख्य-थ्रेड फ़्रेम समय, डिकोड थ्रूपुट, सक्रिय कार्य गणना, कैश हिट दर, रद्दीकरण विलंबता (cancellation latency), और माइग्रेशन से पहले और बाद में कंपाइलर डायग्नोस्टिक्स। इनके बिना, प्रदर्शन प्रतिगमन (performance regression) और शुद्धता सुधार में भ्रमित होना आसान है।

2. पहले Swift 6.2 के परिवर्तनों को समझाएं

Swift 6.2 वैकल्पिक -default-isolation MainActor सेटिंग प्रदान करता है, जो किसी लक्ष्य के कोड को डिफ़ॉल्ट रूप से मुख्य एक्टर के लिए अलग (isolate) करता है। यह समवर्ती रूप से चलने के लिए लक्षित कार्यों को चिह्नित करने के लिए @concurrent भी जोड़ता है। NonisolatedNonsendingByDefault के साथ, एक nonisolated async फ़ंक्शन स्वचालित रूप से वैश्विक समवर्ती निष्पादक (global concurrent executor) में जाने के बजाय डिफ़ॉल्ट रूप से कॉलर के एक्टर को बनाए रखता है; समानांतर सिमेंटिक्स लक्षित होने पर @concurrent का उपयोग करें।

ये विकल्प एनोटेशन के शोर को कम करते हैं लेकिन प्रत्येक ऑपरेशन को समानांतर नहीं बनाते हैं। डिफ़ॉल्ट आइसोलेशन यह बताता है कि कौन सुरक्षित रूप से स्थिति तक पहुंच सकता है। @concurrent स्पष्ट रूप से कहता है कि कार्य वर्तमान एक्टर को छोड़ सकता है और समानांतर में चल सकता है।

3. आइसोलेशन सीमाएं निर्धारित करें

परिवर्तनीय कैश और UI स्थिति को एक @MainActor प्रकार में रखें। अपरिवर्तनीय इनपुट और आउटपुट वाले शुद्ध मान परिवर्तनों और डिकोडर्स को Sendable बनाएं। डाउनलोड लेयर को छिपी हुई थ्रेड आत्मीयता वाली वस्तुओं के बजाय मान वापस करने चाहिए। प्रत्येक API के लिए, कॉलर एक्टर, संरक्षित स्थिति और समानांतर निष्पादन की अनुमति है या नहीं, इसका दस्तावेजीकरण करें।

उदाहरण के लिए, कैश सुरक्षा को मुख्य एक्टर पर रखें:

swift
@MainActor
final class ImageCache {
    private var values: [URL: Image] = [:]

    func value(for url: URL) -> Image? { values[url] }
    func insert(_ image: Image, for url: URL) { values[url] = image }
}

केवल चेतावनियों को शांत करने के लिए पुराने कोड के चारों ओर MainActor.run न बिखेरें। माइग्रेशन मार्गदर्शन इसे एक संक्रमणकालीन उपकरण मानता है; दीर्घकालिक APIs को अपने प्रकारों और हस्ताक्षरों में आइसोलेशन व्यक्त करना चाहिए।

4. तय करें कि @concurrent का उपयोग कब करना है

@concurrent केवल तभी जोड़ें जब कार्य को समानांतर में चलाना सुरक्षित हो, इसके इनपुट और आउटपुट भेजने की आवश्यकताओं (sending requirements) को पूरा करते हों, और बेसलाइन एक लाभ दिखाती हो। इमेज डिकोडिंग अक्सर योग्य होती है: यह सीधे मुख्य-एक्टर कैश को नहीं छूती है और एक नया बनाया गया मान लौटाती है।

swift
@MainActor
struct ImagePipeline {
    static func image(from url: URL) async throws -> Image {
        let cached = cache.value(for: url)
        if let cached { return cached }

        let image = try await decode(url: url)
        cache.insert(image, for: url)
        return image
    }

    @concurrent
    private static func decode(url: URL) async throws -> Image {
        let (data, _) = try await URLSession.shared.data(from: url)
        return try ImageDecoder.decode(data)
    }
}

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

5. कॉलर संदर्भ और Sendable को संभालें

कॉलर-संदर्भ सिमेंटिक्स सक्षम होने के साथ, एक async फ़ंक्शन कॉलर के एक्टर पर जारी रह सकता है। यह तब मदद करता है जब उसे कॉलर-पृथक स्थिति की आवश्यकता होती है, लेकिन लंबा CPU कार्य उस एक्टर को ब्लॉक कर सकता है, इसलिए एक शुद्ध @concurrent चरण को अलग करें। एक्टर्स को पार करने वाले मान Sendable होने चाहिए या किसी एक्टर, लॉक या समकक्ष सिंक्रोनाइज़ेशन सीमा द्वारा संरक्षित होने चाहिए।

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

6. टूलिंग के साथ चरणों में माइग्रेट करें

पहले एक मॉड्यूल में आगामी सुविधाओं और सख्त डायग्नोस्टिक्स को सक्षम करें, चेतावनियों और फ़िक्स-इट्स के लिए माइग्रेशन टूलिंग का उपयोग करें। सार्वजनिक APIs में आइसोलेशन और Sendable बाधाओं को स्थिर करें, फिर उस मॉड्यूल को Swift 6 भाषा मोड पर स्विच करें। प्रत्येक चरण को प्रतिवर्ती (revertible) रखें और मॉड्यूल द्वारा डायग्नोस्टिक्स को संग्रहीत करें।

एक व्यावहारिक क्रम है मान प्रकार और प्रोटोकॉल बाधाएं, निम्न-स्तरीय सेवाएं, कैश एक्टर, और अंत में UI कॉल साइटें। त्रुटियां तब सैकड़ों परिवर्तित await अभिव्यक्तियों के बजाय सीमाओं पर दिखाई देती हैं। अनुकूलता कार्य के दौरान, कार्यान्वयन मॉड्यूल के अंदर जाँच स्तर बढ़ाते हुए मौजूदा क्लाइंट्स के लिए Swift 5-मोड इंटरफेस बनाए रखें।

7. रेस और आकस्मिक सीरियलाइज़ेशन की अनुपस्थिति को साबित करें

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

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

8. रूब्रिक और अनुवर्ती प्रश्न

समझाना आवश्यक है

  • डिफ़ॉल्ट आइसोलेशन, कॉलर संदर्भ और स्पष्ट समानता में अंतर करें; async का अर्थ बैकग्राउंड थ्रेड नहीं है।
  • @unchecked Sendable के साथ त्रुटियों को छिपाने के बजाय Sendable, एक्टर सीमाओं और रद्दीकरण के साथ सुरक्षा साबित करें।
  • रिग्रेशन संकेतों की व्याख्या के साथ चरणबद्ध माइग्रेशन, तनाव परीक्षण और प्रदर्शन आधार रेखाएं प्रदान करें।

अनुवर्ती प्रश्न

  • यदि डिकोडर थ्रेड-सुरक्षित नहीं है, तो क्या आप इसे एक एक्टर, एक सीरियल निष्पादक, या एक आउट-ऑफ़-प्रोसेस सेवा के पीछे रखेंगे, और क्यों?
  • पूरे कैश को क्रमित किए बिना आप एक URL के लिए डुप्लिकेट डाउनलोड को कैसे संयोजित (coalesce) करेंगे?
  • माइग्रेशन के दौरान Swift 5 और Swift 6 मॉड्यूल APIs कैसे साझा कर सकते हैं, और कौन सी बाधाएं पहले भेजी जानी चाहिए?

स्कोरिंग गाइड

एक उत्कृष्ट उत्तर आइसोलेशन मॉडलिंग, माइग्रेशन टूलिंग और रनटाइम साक्ष्य को जोड़ता है: स्थिति और निष्पादन संदर्भ को परिभाषित करें, समानता के लिए सबसे छोटे आवश्यक @concurrent सतह का उपयोग करें, फिर कंपाइलर डायग्नोस्टिक्स, सैनिटाइज़र और बेंचमार्क डेटा के साथ परिणाम को मान्य करें।

सार्वजनिक स्रोत

संबंधित प्रश्न

संबंधित इंटरव्यू टूल

कोडिंग प्रॉम्प्ट के लिए स्क्रीनशॉट का उपयोग करें

समस्या को कैप्चर करें, फिर क्रम से प्रतिबंधों (constraints), समाधान, कोड, एज केस और जटिलता पर काम करें।

टूल देखें