सुरक्षित क्लोजर को लागू करने के लिए कचरा संग्रहण की आवश्यकता है?


14

मैंने हाल ही में प्रोग्रामिंग भाषाओं पर एक ऑनलाइन पाठ्यक्रम में भाग लिया, जिसमें अन्य अवधारणाओं के बीच, क्लोजर प्रस्तुत किए गए थे। मैं अपना प्रश्न पूछने से पहले कुछ संदर्भ देने के लिए इस पाठ्यक्रम से प्रेरित दो उदाहरण लिखता हूं।

पहला उदाहरण एक एसएमएल फ़ंक्शन है जो 1 से x तक की संख्याओं की सूची तैयार करता है, जहां x फ़ंक्शन का पैरामीटर है:

fun countup_from1 (x: int) =
    let
        fun count (from: int) =
            if from = x
            then from :: []
            else from :: count (from + 1)
    in
        count 1
    end

SML REPL में:

val countup_from1 = fn : int -> int list
- countup_from1 5;
val it = [1,2,3,4,5] : int list

countup_from1समारोह सहायक बंद का उपयोग करता है countकि कैप्चर और चर का उपयोग करता है xउसके संदर्भ से।

दूसरे उदाहरण में, जब मैं किसी फ़ंक्शन का आह्वान करता हूं, तो मुझे create_multiplier tएक फ़ंक्शन (वास्तव में, एक क्लोजर) मिलता है जो इसके द्वारा गुणा करता है:

fun create_multiplier t = fn x => x * t

SML REPL में:

- fun create_multiplier t = fn x => x * t;
val create_multiplier = fn : int -> int -> int
- val m = create_multiplier 10;
val m = fn : int -> int
- m 4;
val it = 40 : int
- m 2;
val it = 20 : int

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

अब, जीवन भर ठीक से काम करने के लिए बंद होने के लिए, हमें कैप्चर किए गए चर के जीवनकाल का विस्तार करने की आवश्यकता है t(उदाहरण में यह पूर्णांक है लेकिन यह किसी भी प्रकार का मान हो सकता है)। जहां तक ​​मुझे पता है, एसएमएल में यह कचरा संग्रहण द्वारा संभव बनाया गया है: क्लोजर कैप्चर किए गए मूल्य का संदर्भ रखता है जिसे बाद में कचरा कलेक्टर द्वारा निपटान किया जाता है जब क्लोजर नष्ट हो जाता है।

मेरा प्रश्न: सामान्य तौर पर, कचरा संग्रहण केवल यह सुनिश्चित करने के लिए संभव तंत्र है कि क्लोजर सुरक्षित हैं (अपने पूरे जीवनकाल के दौरान कॉल करने योग्य)?

या क्या अन्य तंत्र हैं जो कचरा संग्रह के बिना बंद होने की वैधता सुनिश्चित कर सकते हैं: कैप्चर किए गए मूल्यों को कॉपी करें और इसे बंद करने के अंदर स्टोर करें? स्वयं को बंद करने के जीवनकाल को प्रतिबंधित करें ताकि इसके पकड़े गए चर समाप्त हो जाने के बाद इसे लागू न किया जा सके?

सबसे लोकप्रिय दृष्टिकोण क्या हैं?

संपादित करें

मुझे नहीं लगता कि ऊपर दिए गए उदाहरण को कैप्चर किए गए चर (ओं) को बंद करके कॉपी किया जा सकता है। सामान्य तौर पर, कैप्चर किए गए चर किसी भी प्रकार के हो सकते हैं, उदाहरण के लिए वे बहुत बड़ी (अपरिवर्तनीय) सूची में बंधे हो सकते हैं। इसलिए, कार्यान्वयन में इन मूल्यों को कॉपी करना बहुत ही अक्षम होगा।

पूर्णता के लिए, संदर्भों (और दुष्प्रभावों) का उपयोग करके यहां एक और उदाहरण दिया गया है:

(* Returns a closure containing a counter that is initialized
   to 0 and is incremented by 1 each time the closure is invoked. *)
fun create_counter () =
    let
        (* Create a reference to an integer: allocate the integer
           and let the variable c point to it. *)
        val c = ref 0
    in
        fn () => (c := !c + 1; !c)
    end

(* Create a closure that contains c and increments the value
   referenced by it it each time it is called. *)
val m = create_counter ();

SML REPL में:

val create_counter = fn : unit -> unit -> int
val m = fn : unit -> int
- m ();
val it = 1 : int
- m ();
val it = 2 : int
- m ();
val it = 3 : int

इसलिए, चर को संदर्भ द्वारा भी कैप्चर किया जा सकता है और फ़ंक्शन कॉल के बाद अभी भी जीवित हैं (उन्हें create_counter ()) पूरा कर लिया है।


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

3
@ बीटीली: रिफार्क्सिंग एक कचरा कलेक्टर के लिए कई अलग-अलग कार्यान्वयन रणनीतियों में से एक है। यह वास्तव में मायने नहीं रखता है कि इस प्रश्न के उद्देश्य के लिए जीसी कैसे लागू किया जाता है।
जॉर्ग डब्ल्यू मित्तग

3
@ बीटीली: "सच" कचरा संग्रह का क्या मतलब है? जीसी को लागू करने का सिर्फ एक और तरीका है रिफंडिंग। ट्रेसिंग अधिक लोकप्रिय है, शायद क्योंकि रीफाउंटिंग के साथ चक्रों को इकट्ठा करने की कठिनाइयों के कारण। (आमतौर पर, आप वैसे भी एक अलग अनुरेखण जीसी के साथ समाप्त होते हैं, इसलिए यदि आप एक के साथ प्राप्त कर सकते हैं तो दो जीसी को लागू करने से क्यों परेशान होते हैं।) लेकिन साइकिल से निपटने के अन्य तरीके हैं। 1) बस उन्हें मना करो। 2) बस उन्हें अनदेखा करें। (यदि आप त्वरित एक-बंद स्क्रिप्ट के लिए एक कार्यान्वयन कर रहे हैं, तो क्यों नहीं?) 3) उन्हें स्पष्ट रूप से पता लगाने का प्रयास करें। (यह पता चला है कि उपलब्ध होने वाले वापसी गति को तेज कर सकती है।)
जॉर्ग डब्ल्यू मित्तग

1
यह इस बात पर निर्भर करता है कि आप पहली जगह में क्लोजर क्यों चाहते हैं। यदि आप लागू करना चाहते हैं, कहते हैं, एक पूर्ण लंबोतरा पथरी शब्दार्थ, आपको निश्चित रूप से जीसी, अवधि की आवश्यकता है । आसपास कोई दूसरा रास्ता नहीं है। यदि आप ऐसा कुछ चाहते हैं, जो दूर से मिलता जुलता है, लेकिन ऐसे (जैसे सी ++, डेल्फी, जो भी हो) के सटीक शब्दार्थ का पालन नहीं करता है - जो भी आप चाहते हैं, क्षेत्र विश्लेषण का उपयोग करें, पूरी तरह से मैनुअल मेमोरी प्रबंधन का उपयोग करें।
SK- तर्क

2
@ मेसन व्हीलर: क्लोज़र केवल मूल्य हैं, सामान्य तौर पर यह अनुमान लगाना संभव नहीं है कि उन्हें रनटाइम के आसपास कैसे स्थानांतरित किया जाएगा। इस अर्थ में, वे कुछ विशेष नहीं हैं, वही एक स्ट्रिंग, एक सूची, और इसी तरह के लिए मान्य होगा।
जियोर्जियो

जवाबों:


14

इस पहलू पर रस्ट प्रोग्रामिंग भाषा दिलचस्प है।

रस्ट एक सिस्टम भाषा है, जिसमें एक वैकल्पिक GC है, और इसे शुरुआत से ही क्लोजर के साथ डिजाइन किया गया था ।

अन्य चर के रूप में, जंग बंद होने के विभिन्न स्वाद आते हैं। स्टैक क्लोजर , सबसे आम वाले, एक-शॉट उपयोग के लिए हैं। वे स्टैक पर रहते हैं और कुछ भी संदर्भित कर सकते हैं। स्वामित्व वाले क्लोजर कैद किए गए चर का स्वामित्व लेते हैं। मुझे लगता है कि वे तथाकथित "एक्सचेंज हीप" पर रहते हैं, जो एक वैश्विक ढेर है। उनका जीवनकाल इस बात पर निर्भर करता है कि उनका मालिक कौन है। प्रबंधित क्लोजर कार्य-स्थानीय ढेर पर रहते हैं, और कार्य के GC द्वारा ट्रैक किए जाते हैं। मैं उनकी कैप्चरिंग सीमाओं के बारे में निश्चित नहीं हूँ, हालाँकि।


1
बहुत दिलचस्प लिंक और जंग भाषा का संदर्भ। धन्यवाद। +1।
जियोर्जियो

1
मैंने उत्तर स्वीकार करने से पहले बहुत सोचा क्योंकि मुझे लगता है कि मेसन का उत्तर बहुत जानकारीपूर्ण है। मैंने इसे चुना है क्योंकि यह जानकारीपूर्ण है और यह बंद करने के लिए एक मूल दृष्टिकोण के साथ कम ज्ञात भाषा का हवाला देता है।
जियोर्जियो

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

9

दुर्भाग्य से GC के साथ शुरुआत करने से आप XY सिंड्रोम का शिकार हो सकते हैं:

  • क्लोजर के लिए क्लोजर की आवश्यकता होती है जब तक वे बंद रहते हैं जब तक क्लोजर करता है (सुरक्षा कारणों से)
  • जीसी का उपयोग करके हम उन चर के जीवनकाल को काफी लंबे समय तक बढ़ा सकते हैं
  • XY सिंड्रोम: क्या जीवनकाल का विस्तार करने के लिए अन्य तंत्र हैं?

नोट, हालांकि, एक चर के जीवनकाल को विस्तारित करने के विचार से एक बंद करने के लिए आवश्यक नहीं है ; यह सिर्फ GC द्वारा लाया गया है; मूल सुरक्षा कथन बस बंद चर पर बंद होना चाहिए (और यहां तक ​​कि अस्थिर है, हम कह सकते हैं कि वे अंतिम बंद होने के बाद तक रहना चाहिए)।

अनिवार्य रूप से, दो दृष्टिकोण हैं जो मैं देख सकता हूं (और वे संभावित रूप से संयुक्त हो सकते हैं ):

  1. बंद चर के जीवनकाल का विस्तार करें (उदाहरण के लिए GC करता है)
  2. बंद के जीवनकाल को प्रतिबंधित करें

उत्तरार्द्ध सिर्फ एक सममित दृष्टिकोण है। यह अक्सर उपयोग नहीं किया जाता है, लेकिन अगर, रुस्त की तरह, आपके पास एक क्षेत्र-जागरूक प्रकार की प्रणाली है, तो यह निश्चित रूप से संभव है।


7

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

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

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


4
"कचरा संग्रह बंद करने के लिए आवश्यक नहीं है।": सवाल यह है कि क्या इसकी आवश्यकता है ताकि भाषा सुरक्षित क्लोजर लागू कर सके। मुझे पता है कि मैं सी ++ में सुरक्षित क्लोजर लिख सकता हूं लेकिन भाषा उन्हें लागू नहीं करती है। बंद चर के जीवनकाल का विस्तार करने वाले बंदों के लिए, मेरे प्रश्न का संपादन देखें।
जियोर्जियो

1
मुझे लगता है कि इस प्रश्न को फिर से शुरू किया जा सकता है: सुरक्षित क्लोजर के लिए
मथिउ एम।

1
शीर्षक में "सुरक्षित क्लोजर" शब्द शामिल है, क्या आपको लगता है कि मैं इसे बेहतर तरीके से तैयार कर सकता हूं?
जियोर्जियो

1
क्या आप दूसरे पैराग्राफ को सही कर सकते हैं? एसएमएल में, क्लोजर उन डेटा के जीवनकाल का विस्तार करते हैं जो कैप्चर किए गए चर द्वारा संदर्भित होते हैं। इसके अलावा, यह सच है कि आप चर (उनके बंधन को बदल) असाइन नहीं कर सकते हैं, लेकिन आपके पास परिवर्तनशील डेटा ( ref's) के माध्यम से है । तो, ठीक है, एक बहस कर सकता है कि क्लोजर का कार्यान्वयन कचरा संग्रहण से संबंधित है या नहीं, लेकिन उपरोक्त कथनों को सही किया जाना चाहिए।
जियोर्जियो

1
@ जियोर्जियो: अब के बारे में कैसे? इसके अलावा, किस अर्थ में आप मेरे कथन को पाते हैं जो बंद किए गए चर के जीवनकाल को गलत तरीके से विस्तारित करने की आवश्यकता नहीं है? जब आप परिवर्तनशील डेटा के बारे में बात करते हैं, तो आप संदर्भ प्रकारों ( refओं, सरणियों, आदि) के बारे में बात कर रहे हैं जो एक संरचना की ओर इशारा करते हैं। लेकिन मूल्य ही संदर्भ है, न कि वह जिस चीज की ओर इशारा करता है। यदि आपके पास है var a = ref 1और आप एक प्रति बनाते हैं var b = a, और आप उपयोग करते हैं b, तो क्या इसका मतलब है कि आप अभी भी उपयोग कर रहे हैं a? नहीं। आपके पास उसी संरचना तक पहुंच है, जिसके द्वारा इंगित किया गया है a? हां। यह है कि कैसे इन प्रकार SML में काम करते हैं और बंद करने के साथ कुछ नहीं करना है
user102008

6

क्लोजर लागू करने के लिए कचरा संग्रहण आवश्यक नहीं है। 2008 में, डेल्फी भाषा, जिसे कचरा एकत्र नहीं किया जाता है, ने क्लोजर के कार्यान्वयन को जोड़ा। यह इस तरह काम करता है:

कंपाइलर हुड के नीचे एक फ़नकार ऑब्जेक्ट बनाता है जो एक क्लोजर का प्रतिनिधित्व करने वाले इंटरफ़ेस को लागू करता है। सभी बंद ओवर-लोकल वैरिएबल स्थानीय लोगों से फ़ंक्शनल ऑब्जेक्ट पर फ़ील्ड्स को संलग्न करने की प्रक्रिया के लिए बदल जाते हैं। यह सुनिश्चित करता है कि राज्य तब तक संरक्षित है जब तक कि फ़नकार है।

इस प्रणाली की सीमा यह है कि किसी भी पैरामीटर को एन्क्लोजिंग फ़ंक्शन के संदर्भ में पारित किया गया है, साथ ही फ़ंक्शन के परिणाम मूल्य, को फ़नकार द्वारा कब्जा नहीं किया जा सकता है क्योंकि वे स्थानीय नहीं हैं, जिसका दायरा एन्क्लोज़िंग फ़ंक्शन तक सीमित है।

फंक्शनल को क्लोजर संदर्भ द्वारा संदर्भित किया जाता है, इसे इंटरफेस के बजाय फ़ंक्शन पॉइंटर की तरह डेवलपर को देखने के लिए सिंटैक्टिक शुगर का उपयोग किया जाता है। यह इंटरफेसियों के लिए डेल्फी के संदर्भ-गिनती प्रणाली का उपयोग करता है ताकि यह सुनिश्चित किया जा सके कि जब तक इसकी आवश्यकता होती है तब तक फ़नकार ऑब्जेक्ट (और यह सभी स्थिति जो भी हो) "जीवित" रहता है, और फिर रिफंड के 0 पर गिर जाने पर यह मुक्त हो जाता है।


1
आह, इसलिए यह केवल स्थानीय चर पर कब्जा करना संभव है, न कि तर्क! यह एक उचित और चतुर व्यापार बंद लगता है! +1
जियोर्जियो

1
@ जियोर्जियो: यह तर्क को पकड़ सकता है, बस वो नहीं जो कि var पैरामीटर हैं।
मेसन व्हीलर

2
आप साझा निजी राज्य के माध्यम से संवाद करने वाले 2 क्लोजर रखने की क्षमता भी खो देते हैं। आप बुनियादी उपयोग के मामलों में इसका सामना नहीं करेंगे, लेकिन यह जटिल सामान करने की आपकी क्षमता को सीमित करता है। फिर भी क्या संभव है इसका महान उदाहरण!
btilly

3
@ बीटीली: वास्तव में, यदि आप एक ही एन्कोडिंग फ़ंक्शन के अंदर 2 क्लोजर रखते हैं, तो यह पूरी तरह से कानूनी है। वे अंत में एक ही फ़नकार वस्तु को साझा करते हैं, और यदि वे एक दूसरे के समान राज्य को संशोधित करते हैं, तो एक में परिवर्तन दूसरे में परिलक्षित होगा।
मेसन व्हीलर

2
@MasonWheeler: "नहीं। कचरा संग्रह प्रकृति में गैर-नियतात्मक है; इस बात की कोई गारंटी नहीं है कि किसी भी दी गई वस्तु को कभी भी एकत्रित किया जाएगा, ऐसा होने पर अकेले रहने दें। लेकिन संदर्भ की गणना नियतात्मक है: आप संकलक द्वारा गारंटीकृत हैं कि वस्तु। गिनती ०. 0. पर गिरने के तुरंत बाद मुक्त कर दिया जाएगा। अगर मुझे हर बार ऐसा लगता है कि मैंने मिथक को तोड़ दिया है। OCaml में एक नियतांक GC है। सी ++ थ्रेड सुरक्षित shared_ptrगैर-नियतात्मक है क्योंकि विनाशकारी शून्य से घटने की दौड़ करते हैं।
जॉन हैरोप
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.