จำเป็นต้องมีการรวบรวมขยะหรือไม่สำหรับการดำเนินการปิดอย่างปลอดภัยหรือไม่?


14

ฉันเพิ่งเข้าร่วมหลักสูตรออนไลน์เกี่ยวกับภาษาการเขียนโปรแกรมซึ่งมีการนำเสนอการปิด ฉันเขียนสองตัวอย่างที่ได้รับแรงบันดาลใจจากหลักสูตรนี้เพื่อให้บริบทก่อนถามคำถาม

ตัวอย่างแรกคือฟังก์ชัน SML ที่สร้างรายการตัวเลขจาก 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ฉันได้รับกลับมาฟังก์ชั่น (อันที่จริงการปิด) ที่ทวีคูณอาร์กิวเมนต์โดย 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(ในตัวอย่างเป็นจำนวนเต็ม แต่อาจเป็นค่าใด ๆ ก็ได้) เท่าที่ฉันรู้ใน SML สิ่งนี้เกิดขึ้นได้จากการรวบรวมขยะ: การปิดช่วยให้การอ้างอิงถึงค่าที่บันทึกไว้ซึ่งถูกเก็บรวบรวมโดยผู้เก็บขยะในเวลาต่อมาเมื่อการทำลายถูกปิด

คำถามของฉัน: โดยทั่วไปการเก็บขยะเป็นเพียงกลไกเดียวที่เป็นไปได้เพื่อให้แน่ใจว่าการปิดนั้นปลอดภัย (สามารถโทรได้ตลอดชีวิต)

หรืออะไรคือกลไกอื่น ๆ ที่สามารถรับรองความถูกต้องของการปิดโดยไม่มีการเก็บขยะ: คัดลอกค่าที่จับและเก็บไว้ในการปิด จำกัด อายุการใช้งานของการปิดตัวเองเพื่อที่จะไม่สามารถเรียกใช้หลังจากตัวแปรที่จับได้หมดอายุหรือไม่

อะไรคือแนวทางที่ได้รับความนิยมมากที่สุด

แก้ไข

ฉันไม่คิดว่าตัวอย่างข้างต้นสามารถอธิบาย / นำไปปฏิบัติได้โดยการคัดลอกตัวแปรที่จับไปยังการปิด โดยทั่วไปแล้วตัวแปรที่จับได้สามารถเป็นประเภทใดก็ได้เช่นพวกเขาสามารถผูกกับรายการที่มีขนาดใหญ่มาก (ไม่เปลี่ยนรูป) ดังนั้นในการดำเนินการมันจะไม่มีประสิทธิภาพมากในการคัดลอกค่าเหล่านี้

เพื่อความสมบูรณ์นี่เป็นอีกตัวอย่างหนึ่งที่ใช้การอ้างอิง (และผลข้างเคียง):

(* 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
ตัวแปรใด ๆ ที่ถูกปิดควรได้รับการปกป้องจากการรวบรวมขยะและตัวแปรใด ๆ ที่ไม่ได้ปิดควรมีสิทธิ์ได้รับการรวบรวมขยะ มันตามมาว่ากลไกใด ๆ ที่สามารถติดตามได้อย่างน่าเชื่อถือว่าตัวแปรปิดอยู่หรือไม่ยังสามารถเรียกคืนหน่วยความจำที่ตัวแปรนั้นใช้ได้อย่างน่าเชื่อถือ
Robert Harvey

3
@btilly: การนับซ้ำเป็นเพียงหนึ่งในกลยุทธ์การใช้งานที่แตกต่างกันสำหรับตัวรวบรวมขยะ ไม่สำคัญว่าจะมีการนำ GC ไปใช้เพื่อจุดประสงค์ของคำถามนี้อย่างไร
Jörg W Mittag

3
@btilly: การเก็บขยะ "จริง" หมายความว่าอย่างไร การนับใหม่เป็นอีกวิธีหนึ่งในการนำ GC มาใช้ การติดตามเป็นที่นิยมมากขึ้นอาจเป็นเพราะความยากลำบากในการรวบรวมรอบด้วยการนับซ้ำ (โดยทั่วไปคุณจะต้องแยกการติดตาม GC ต่อไปดังนั้นทำไมต้องใช้ GC สองตัวหากคุณสามารถทำได้โดยใช้หนึ่งตัว) แต่มีวิธีอื่นในการจัดการกับรอบ 1) ห้ามเพียงแค่พวกเขา 2) เพียงแค่ละเว้นพวกเขา (หากคุณกำลังใช้งานสำหรับสคริปต์แบบครั้งเดียวอย่างรวดเร็วทำไมไม่ใช้) 3) พยายามตรวจหาสคริปต์อย่างชัดเจน (ปรากฎว่ามี refcount ความเร็วสามารถพร้อมใช้งานที่เพิ่มขึ้น.)
Jörg W Mittag

1
ขึ้นอยู่กับสาเหตุที่คุณต้องการปิดในครั้งแรก หากคุณต้องการนำไปใช้พูดความหมายแคลคูลัสแลมบ์ดาเต็มรูปแบบคุณต้องมี GC, คาบ ไม่มีวิธีอื่น ๆ หากคุณต้องการบางสิ่งที่มีลักษณะคล้ายกับการปิด แต่ไม่เป็นไปตามความหมายที่แน่นอนของสิ่งนั้น (เช่นใน C ++, Delphi, อะไรก็ตาม) - ทำทุกสิ่งที่คุณต้องการใช้การวิเคราะห์ภูมิภาคใช้การจัดการหน่วยความจำด้วยตนเองอย่างเต็มที่
SK-logic

2
@Mason Wheeler: การปิดเป็นเพียงค่าโดยทั่วไปเป็นไปไม่ได้ที่จะทำนายว่าพวกมันจะถูกย้ายไปที่รันไทม์อย่างไร ในแง่นี้มันไม่มีอะไรพิเศษเหมือนกันจะใช้ได้กับสตริงรายการและอื่น ๆ
Giorgio

คำตอบ:


14

ภาษาโปรแกรม Rust นั้นน่าสนใจในส่วนนี้

Rust เป็นภาษาของระบบพร้อมตัวเลือก GC และถูกออกแบบมาพร้อมกับการปิดตั้งแต่ต้น

เช่นเดียวกับตัวแปรอื่น ๆ การปิดของสนิมนั้นมีหลากหลายรสชาติ การปิดกองซ้อนที่พบมากที่สุดสำหรับการใช้งานครั้งเดียว พวกมันอาศัยอยู่บนสแต็กและสามารถอ้างอิงอะไรก็ได้ การปิดที่เป็นเจ้าของจะถือกรรมสิทธิ์ของตัวแปรที่ถูกจับ ฉันคิดว่าพวกเขาอาศัยอยู่บน "แลกเปลี่ยนกอง" ซึ่งเป็นกองโลก อายุขัยของพวกเขาขึ้นอยู่กับว่าใครเป็นเจ้าของ การปิดที่ได้รับการจัดการจะอาศัยอยู่บนฮีปของ task-local และถูกติดตามโดย GC ของภารกิจ แต่ฉันไม่แน่ใจเกี่ยวกับข้อ จำกัด ในการถ่ายภาพ


1
ลิงค์ที่น่าสนใจมากและอ้างอิงถึงภาษา Rust ขอบคุณ +1
Giorgio

1
ฉันคิดมากก่อนที่จะยอมรับคำตอบเพราะฉันพบว่าคำตอบของเมสันให้ข้อมูลมาก ฉันเลือกภาษานี้เพราะทั้งเป็นข้อมูลและอ้างอิงภาษาที่รู้จักน้อยกว่าโดยใช้วิธีดั้งเดิมในการปิด
Giorgio

ขอบคุณสำหรับสิ่งนั้น ฉันมีความกระตือรือร้นมากเกี่ยวกับภาษาเด็กนี้และฉันยินดีที่จะแบ่งปันความสนใจของฉัน ฉันไม่ทราบว่าการปิดที่ปลอดภัยนั้นเป็นไปได้โดยปราศจาก GC ก่อนที่ฉันจะได้ยินเกี่ยวกับสนิม
barjak

9

น่าเสียดายที่การเริ่มต้นด้วย GC ทำให้คุณตกเป็นเหยื่อของ XY syndrom:

  • การปิดต้องการมากกว่าตัวแปรที่ปิดอยู่ตราบใดที่การปิดทำได้ (เพื่อเหตุผลด้านความปลอดภัย)
  • การใช้ GC เราสามารถยืดอายุการใช้งานของตัวแปรเหล่านั้นให้นานพอ
  • XY อาการ: มีกลไกอื่น ๆ ที่จะยืดอายุการใช้งานหรือไม่?

อย่างไรก็ตามโปรดทราบว่าแนวคิดเรื่องการยืดอายุของตัวแปรนั้นไม่จำเป็นสำหรับการปิด มันเพิ่งมาโดย GC; ข้อความด้านความปลอดภัยดั้งเดิมเป็นเพียงตัวแปรปิดที่ควรมีชีวิตอยู่ตราบใดที่การปิด (และแม้จะสั่นคลอนเราก็สามารถพูดได้ว่าพวกเขาควรมีชีวิตอยู่จนกว่าจะมีการปิดการใช้งานครั้งสุดท้าย)

โดยพื้นฐานแล้วมีสองวิธีที่ฉันเห็น (และพวกเขาอาจรวมกันได้):

  1. ยืดอายุการใช้งานของตัวแปรปิด (เช่นที่ GC ทำ)
  2. จำกัด อายุการใช้งานของการปิด

หลังเป็นเพียงวิธีการสมมาตร มันไม่ได้ใช้บ่อย แต่ถ้าเช่นเดียวกับรัสคุณมีระบบการพิมพ์ตามภูมิภาคแล้วก็เป็นไปได้อย่างแน่นอน


7

การรวบรวมขยะไม่จำเป็นสำหรับการปิดอย่างปลอดภัยเมื่อจับตัวแปรตามค่า ตัวอย่างที่โดดเด่นหนึ่งคือ C ++ C ++ ไม่มีการรวบรวมขยะมาตรฐาน Lambdas ใน C ++ 11 คือการปิด (พวกเขาจับตัวแปรท้องถิ่นจากขอบเขตโดยรอบ) ตัวแปรแต่ละตัวที่ถูกจับโดยแลมบ์ดาสามารถระบุให้จับโดยค่าหรือโดยการอ้างอิง หากมีการอ้างอิงโดยการอ้างอิงคุณสามารถพูดได้ว่าไม่ปลอดภัย อย่างไรก็ตามหากตัวแปรถูกดักจับโดยค่ามันก็จะปลอดภัยเพราะสำเนาที่จับและตัวแปรดั้งเดิมนั้นแยกจากกันและมีอายุการใช้งานที่อิสระ

ในตัวอย่าง SML ที่คุณให้นั้นเป็นเรื่องง่ายที่จะอธิบาย: ตัวแปรถูกจับด้วยค่า ไม่จำเป็นต้อง "ยืดอายุการใช้งาน" ของตัวแปรใด ๆ เนื่องจากคุณสามารถคัดลอกค่าลงในการปิด สิ่งนี้เป็นไปได้เพราะใน ML ตัวแปรไม่สามารถกำหนดให้ ดังนั้นจึงไม่มีความแตกต่างระหว่างสำเนาเดียวกับสำเนาอิสระมากมาย แม้ว่า SML จะมีการรวบรวมขยะ แต่ก็ไม่เกี่ยวข้องกับการรวบรวมตัวแปรโดยการปิด

การรวบรวมขยะไม่จำเป็นสำหรับการปิดอย่างปลอดภัยเมื่อจับตัวแปรโดยอ้างอิง (ชนิด) ตัวอย่างหนึ่งคือส่วนขยายของ Apple Blocks สำหรับภาษา C, C ++, Objective-C และ Objective-C ++ ไม่มีการรวบรวมขยะมาตรฐานใน C และ C ++ บล็อกตัวแปรการจับภาพตามค่าเริ่มต้น อย่างไรก็ตามหากมีการประกาศตัวแปรท้องถิ่น__blockบล็อกก็จะจับพวกมันดูเหมือนว่า "โดยการอ้างอิง" และพวกมันก็ปลอดภัย - พวกมันสามารถใช้งานได้แม้ว่าจะมีการกำหนดขอบเขตของบล็อกแล้วสิ่งที่เกิดขึ้นที่นี่ก็คือ__blockตัวแปรนั้น โครงสร้างพิเศษภายใต้และเมื่อคัดลอกบล็อก (บล็อกต้องถูกคัดลอกเพื่อใช้นอกขอบเขตในสถานที่แรก) พวกเขา "ย้าย" โครงสร้างสำหรับ__block ตัวแปรในฮีปและบล็อกจัดการหน่วยความจำฉันเชื่อว่าผ่านการนับการอ้างอิง


4
"ไม่จำเป็นต้องเก็บรวบรวมขยะสำหรับการปิด": คำถามคือว่าจำเป็นหรือไม่เพื่อให้ภาษาสามารถบังคับใช้การปิดที่ปลอดภัยได้ ฉันรู้ว่าฉันสามารถเขียนการปิดอย่างปลอดภัยใน C ++ แต่ภาษานั้นไม่บังคับใช้ สำหรับการปิดที่ยืดอายุการใช้งานของตัวแปรที่จับได้ดูการแก้ไขคำถามของฉัน
Giorgio

1
ฉันคิดว่าคำถามที่อาจจะ reworded เป็น: สำหรับการปิดปลอดภัย
Matthieu M.

1
ชื่อประกอบด้วยคำว่า "การปิดอย่างปลอดภัย" คุณคิดว่าฉันสามารถกำหนดมันด้วยวิธีที่ดีกว่าได้หรือไม่?
Giorgio

1
คุณช่วยแก้ไขย่อหน้าที่สองได้ไหม? ใน SML การปิดจะช่วยยืดอายุการใช้งานของข้อมูลที่อ้างอิงโดยตัวแปรที่บันทึกไว้ นอกจากนี้มันเป็นความจริงที่คุณไม่สามารถกำหนดตัวแปร (เปลี่ยนการเชื่อมโยง) แต่คุณมีข้อมูลที่ไม่แน่นอน (ผ่านref) ดังนั้นตกลงหนึ่งสามารถอภิปรายว่าการดำเนินการปิดที่เกี่ยวข้องกับการเก็บขยะหรือไม่ แต่งบข้างต้นควรได้รับการแก้ไข
Giorgio

1
@Giorgio: แล้วตอนนี้ล่ะ? นอกจากนี้คุณรู้สึกอย่างไรกับคำแถลงของฉันที่การปิดไม่จำเป็นต้องยืดอายุการใช้งานของตัวแปรที่บันทึกไว้ไม่ถูกต้อง? เมื่อคุณพูดถึงข้อมูลที่ไม่แน่นอนคุณกำลังพูดถึงประเภทอ้างอิง ( refs, อาร์เรย์, ฯลฯ ) ที่ชี้ไปที่โครงสร้าง แต่คุณค่าคือการอ้างอิงเองไม่ใช่สิ่งที่ชี้ไป หากคุณมีvar a = ref 1และคุณทำสำเนาvar b = aและคุณใช้bหมายความว่าคุณยังใช้อยู่aหรือไม่ไม่ คุณสามารถเข้าถึงโครงสร้างเดียวกันที่ชี้ไปaใช่หรือไม่ นั่นเป็นเพียงวิธีการทำงานของประเภทเหล่านี้ใน SML และไม่มีอะไรเกี่ยวข้องกับการปิด
user102008

6

การรวบรวมขยะไม่จำเป็นต้องใช้เพื่อการปิด ในปี 2008 ภาษาเดลฟายซึ่งไม่ได้เก็บขยะได้เพิ่มการดำเนินการปิด มันทำงานได้เช่นนี้:

คอมไพเลอร์สร้างวัตถุ functor ภายใต้ประทุนที่ใช้อินเทอร์เฟซแทนการปิด ตัวแปรโลคัลปิดแล้วทั้งหมดได้รับการเปลี่ยนจากท้องถิ่นสำหรับโพรซีเดอร์การล้อมเป็นฟิลด์บนวัตถุ functor สิ่งนี้ทำให้มั่นใจได้ว่าสถานะจะถูกเก็บรักษาไว้ตราบเท่าที่ functor เป็น

ข้อ จำกัด ของระบบนี้คือพารามิเตอร์ใด ๆ ที่ส่งผ่านโดยการอ้างอิงไปยังฟังก์ชันการปิดล้อมรวมถึงค่าผลลัพธ์ของฟังก์ชันไม่สามารถถูกจับได้โดย functor เนื่องจากไม่ใช่ฟังก์ชันเฉพาะที่ขอบเขตถูก จำกัด ไว้กับฟังก์ชันล้อมรอบ

functor ถูกอ้างถึงโดยการอ้างอิงการปิดโดยใช้ syntactic sugar เพื่อให้นักพัฒนาดูเหมือนเป็นตัวชี้ฟังก์ชันแทนที่จะเป็นอินเตอร์เฟส มันใช้ระบบการนับการอ้างอิงของ Delphi สำหรับอินเทอร์เฟซเพื่อให้แน่ใจว่าวัตถุ functor (และสถานะทั้งหมดที่เก็บ) ยังคง "มีชีวิตอยู่" ตราบเท่าที่มันต้องการและจากนั้นมันจะได้รับการปลดปล่อยเมื่อ refcount ลดลงเป็น 0


1
อาดังนั้นมันเป็นไปได้ที่จะจับตัวแปรเฉพาะที่ไม่ใช่อาร์กิวเมนต์! นี่ดูเหมือนจะเป็นการค้าที่สมเหตุสมผลและชาญฉลาด! +1
Giorgio

1
@Giorgio: มันสามารถจับข้อโต้แย้งไม่ใช่คนที่เป็นพารามิเตอร์var
Mason Wheeler

2
นอกจากนี้คุณยังสูญเสียความสามารถในการมี 2 การปิดที่สื่อสารผ่านรัฐส่วนตัวที่ใช้ร่วมกัน คุณจะไม่พบสิ่งนั้นในกรณีการใช้งานพื้นฐาน แต่มันจำกัดความสามารถของคุณในการทำสิ่งที่ซับซ้อน ยังคงเป็นตัวอย่างที่ดีของสิ่งที่เป็นไปได้!
btilly

3
@btilly: ที่จริงแล้วถ้าคุณใส่ 2 closures ภายในฟังก์ชั่นการปิดล้อมเดียวกันนั่นเป็นสิ่งที่ถูกกฎหมายอย่างสมบูรณ์ พวกเขาจบการแชร์วัตถุ functor เดียวกันและหากพวกเขาแก้ไขสถานะเดียวกันกับแต่ละอื่น ๆ การเปลี่ยนแปลงในหนึ่งจะสะท้อนให้เห็นในอีก
Mason Wheeler

2
@MasonWheeler: "ไม่ใช่การรวบรวมขยะไม่สามารถกำหนดได้ในธรรมชาติไม่มีการรับประกันว่าวัตถุใด ๆ ที่ถูกรวบรวมจะถูกเก็บไว้โดยลำพังเมื่อมันเกิดขึ้น แต่การนับการอ้างอิงเป็นสิ่งที่กำหนด: คุณรับประกันโดยคอมไพเลอร์ว่าวัตถุ จะถูกปล่อยให้เป็นอิสระทันทีหลังจากการนับตกถึง 0 " หากฉันมีค่าเล็กน้อยสำหรับทุกครั้งที่ฉันได้ยินตำนานนั้นชุลมุน OCaml มี GC ที่กำหนดขึ้นแล้ว ความปลอดภัยของเธรด C ++ shared_ptrนั้นไม่สามารถกำหนดได้เนื่องจาก destructors จะลดลงเหลือศูนย์
Jon Harrop
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.