คนเก็บขยะหลีกเลี่ยงการวนซ้ำที่ไม่สิ้นสุดที่นี่ได้อย่างไร?


101

พิจารณาโปรแกรม C # ต่อไปนี้ฉันส่งไปที่ codegolf เพื่อเป็นคำตอบในการสร้างลูปโดยไม่ต้องวนซ้ำ:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

โปรแกรมนี้ดูเหมือนการวนซ้ำที่ไม่มีที่สิ้นสุดในการตรวจสอบของฉัน แต่ดูเหมือนว่าจะทำงานซ้ำหลายพันครั้งจากนั้นโปรแกรมจะสิ้นสุดลงโดยไม่มีข้อผิดพลาด (ไม่มีข้อผิดพลาดเกิดขึ้น) เป็นการละเมิดข้อกำหนดที่ผู้สรุปPในที่สุดจะไม่เรียกหรือไม่

เห็นได้ชัดว่านี่เป็นรหัสโง่ ๆ ที่ไม่ควรปรากฏ แต่ฉันอยากรู้ว่าโปรแกรมจะทำสำเร็จได้อย่างไร

โพสต์รหัสเดิมกอล์ฟ :: /codegolf/33196/loop-without-looping/33218#33218


49
ฉันกลัวที่จะเรียกใช้สิ่งนี้
Eric Scherrer

6
นั่น finalizer ไม่ได้เรียกว่าเป็นแน่นอนภายในขอบเขตของพฤติกรรมที่ถูกต้อง ฉันไม่รู้ว่าทำไมถึงต้องทำซ้ำหลายพันครั้ง แต่ฉันคาดหวังว่าจะไม่มีการเรียกใช้

27
CLR มีการป้องกันเธรด Finalizer ที่ไม่สามารถทำงานให้เสร็จได้ มันจะยุติลงอย่างแรงหลังจากผ่านไป 2 วินาที
Hans Passant

2
ดังนั้นคำตอบที่แท้จริงสำหรับคำถามของคุณในชื่อเรื่องก็คือการหลีกเลี่ยงมันโดยปล่อยให้ลูปที่ไม่มีที่สิ้นสุดทำงานเป็นเวลา 40 วินาทีจากนั้นจึงยุติ
Lasse V.Karlsen

4
จากการลองใช้งานดูเหมือนว่าโปรแกรมจะฆ่าทุกอย่างหลังจากผ่านไป 2 วินาทีไม่ว่าจะเกิดอะไรขึ้นก็ตาม อันที่จริงถ้าคุณยังคงวางไข่เธรดมันจะคงอยู่ได้นานขึ้นเล็กน้อย :)
Michael B

คำตอบ:


110

ตามริกเตอร์ใน CLR รุ่นที่สองผ่าน C # (ใช่ฉันต้องอัปเดต):

หน้า 478

สำหรับ (CLR กำลังปิด) แต่ละวิธีFinalizeจะได้รับประมาณสองวินาทีในการย้อนกลับ หากวิธีการFinalizeไม่กลับมาภายในสองวินาที CLR ก็จะฆ่ากระบวนการนั้นโดยจะไม่มีการเรียกใช้เมธอดFinalizeอีกต่อไป นอกจากนี้หากใช้เวลามากกว่า 40 วินาทีในการเรียกวิธีการสรุปของวัตถุทั้งหมดอีกครั้ง CLR ก็จะฆ่ากระบวนการ

นอกจากนี้ตามที่ Servy กล่าวถึงก็มีหัวข้อของตัวเอง


5
แต่ละวิธีการสรุปในรหัสนี้ใช้เวลาอย่างมากไม่เกิน 40 วินาทีต่อวัตถุ ออบเจ็กต์ใหม่ถูกสร้างขึ้นจากนั้นจึงมีสิทธิ์ได้รับการสรุปไม่เกี่ยวข้องกับโปรแกรมสุดท้ายปัจจุบัน
Jacob Krall

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

แค่คิดถึงสิ่งนี้ไม่ได้ล้างคิวที่ไม่สามารถเข้าถึงได้เหมือนกับ "นอกจากนี้หากใช้เวลามากกว่า 40 วินาทีในการเรียกวิธีการสรุปของวัตถุทั้งหมดอีกครั้ง CLR ก็จะฆ่ากระบวนการนี้"
Eric Scherrer

23

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


หากโปรแกรมปิดท้ายยังไม่เสร็จสิ้น 40 วินาทีหลังจากโปรแกรมควรออกเนื่องจากไม่มีเธรดหลักอยู่การทำงานจะถูกยกเลิกและกระบวนการจะสิ้นสุดลง นี่เป็นค่าเก่าแม้ว่า Microsoft อาจปรับเปลี่ยนตัวเลขจริงหรือแม้แต่อัลกอริทึมทั้งหมดในตอนนี้ Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse

@ LasseV.Karlsen เป็นเอกสารพฤติกรรมของภาษาหรือเพียงแค่วิธีที่ MS เลือกใช้ Finalizers เป็นรายละเอียดการใช้งาน? ฉันคาดหวังอย่างหลัง
Servy

ฉันคาดหวังอย่างหลังเช่นกัน การอ้างอิงอย่างเป็นทางการที่สุดเกี่ยวกับพฤติกรรมนี้ที่ฉันเคยเห็นคือสิ่งที่ Eric โพสต์ไว้ข้างต้นในคำตอบของเขาจากหนังสือ CLR ผ่าน C # โดย Jeffrey Richter
Lasse V.Karlsen

8

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

คนเก็บขยะส่วนใหญ่ทำงานในลักษณะคล้ายรุ่นแรกกว้างในหัวข้อย่อย ในกรณีส่วนใหญ่อาจใช้เวลาหลายชั่วโมงก่อนที่วัตถุจะถูกรีไซเคิล

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

เมื่อฉันรันโค้ดของคุณในcsharpสภาพแวดล้อมแบบโต้ตอบฉันมี:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

ดังนั้นโปรแกรมของคุณจึงล้มเหลวเนื่องจากstdoutถูกปิดกั้นโดยการยุติสภาพแวดล้อม

เมื่อลบConsole.WriteLineและฆ่าโปรแกรม หลังจากผ่านไปห้าวินาทีโปรแกรมจะยุติลง (กล่าวอีกนัยหนึ่งคือคนเก็บขยะยอมแพ้และจะเพิ่มหน่วยความจำทั้งหมดโดยไม่ต้องคำนึงถึงขั้นสุดท้าย)


สิ่งนี้น่าสนใจที่ csharp แบบโต้ตอบระเบิดขึ้นด้วยเหตุผลที่แตกต่างกันอย่างสิ้นเชิง ส่วนย่อยของโปรแกรมเดิมไม่มีการเขียนคอนโซลฉันสงสัยว่ามันจะยุติด้วยหรือไม่
Michael B

@ MichaelB: ฉันได้ทดสอบสิ่งนี้เช่นกัน (ดูความคิดเห็นด้านล่าง) รอห้าวินาทีแล้วจึงยุติ ฉันเดาว่าตัวสุดท้ายของPอินสแตนซ์แรกหมดเวลา
Willem Van Onsem
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.