ซอมบี้มีอยู่จริงหรือไม่…ใน. NET?


385

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

ฉันคิดว่าฉันได้รับส่วนสำคัญนี้ แต่ถ้าฉันไม่อยู่โปรดแจ้งให้เราทราบ แนวคิดนี้สมเหตุสมผลสำหรับฉัน ฉันไม่มั่นใจอย่างสมบูรณ์ว่านี่เป็นสถานการณ์จริงที่อาจเกิดขึ้นใน. NET ฉันไม่เคยได้ยินชื่อ "ซอมบี้" มาก่อน แต่ฉันจำได้ดีว่าโปรแกรมเมอร์ที่ทำงานในระดับลึกลงไปมีแนวโน้มที่จะมีความเข้าใจพื้นฐานการใช้คอมพิวเตอร์ (เช่นการทำเกลียว) ฉันเห็นคุณค่าในการล็อคแน่นอนและฉันได้เห็นโปรแกรมเมอร์ระดับโลกหลายคนใช้ประโยชน์จากการล็อค ฉันยังมีความสามารถ จำกัด ในการประเมินสิ่งนี้ด้วยตัวเองเพราะฉันรู้ว่าlock(obj)คำแถลงนั้นเป็นเพียงแค่ประโยคน้ำตาลสำหรับ:

bool lockWasTaken = false;
var temp = obj;
try { Monitor.Enter(temp, ref lockWasTaken); { body } }
finally { if (lockWasTaken) Monitor.Exit(temp); }

และเพราะMonitor.Enterและมีการทำเครื่องหมายMonitor.Exit externดูเหมือนเป็นไปได้ว่า. NET ทำการประมวลผลบางอย่างที่ป้องกันเธรดจากการสัมผัสกับส่วนประกอบของระบบที่อาจมีผลกระทบประเภทนี้ แต่นั่นเป็นการเก็งกำไรอย่างหมดจดและอาจเป็นไปตามข้อเท็จจริงที่ว่าฉันไม่เคยได้ยินเรื่อง "zombie threads" ก่อน. ดังนั้นฉันหวังว่าฉันจะได้รับคำติชมเกี่ยวกับสิ่งนี้ที่นี่:

  1. มีคำจำกัดความที่ชัดเจนของ "เธรดซอมบี้" มากกว่าที่ฉันได้อธิบายไว้ที่นี่หรือไม่?
  2. เธรดซอมบี้สามารถเกิดขึ้นได้บน. NET (ทำไม / เพราะเหตุใด?)
  3. หากทำได้ฉันจะบังคับให้มีการสร้างเธรดซอมบี้ใน. NET ได้อย่างไร
  4. หากทำได้ฉันจะยกระดับการล็อคโดยไม่เสี่ยงต่อสถานการณ์ของเธรด zombie ใน. NET ได้อย่างไร

ปรับปรุง

ฉันถามคำถามนี้เมื่อสองปีก่อน วันนี้สิ่งนี้เกิดขึ้น:

วัตถุอยู่ในสถานะซอมบี้


8
คุณแน่ใจหรือว่าเพื่อนร่วมงานของคุณไม่ได้พูดถึงการหยุดชะงัก
Andreas Niedermair

10
@ AndreasNiedermair - ฉันรู้ว่าการหยุดชะงักคืออะไรและเห็นได้ชัดว่าไม่ใช่เรื่องของการใช้คำศัพท์ในทางที่ผิด Deadlocking ถูกกล่าวถึงในการสนทนาและแตกต่างอย่างชัดเจนจาก "zombie thread" สำหรับฉันความแตกต่างที่สำคัญคือล็อคตายมีการพึ่งพาสองทางที่ไม่สามารถแก้ไขได้ในขณะที่เธรดซอมบี้เป็นทางเดียวและต้องใช้กระบวนการที่ถูกยกเลิก หากคุณไม่เห็นด้วยและคิดว่ามีวิธีที่ดีกว่าในการดูสิ่งเหล่านี้โปรดอธิบาย
smartcaveman

19
ฉันคิดว่าคำว่า "ซอมบี้" จริง ๆ แล้วมาจากพื้นหลังของ UNIX เช่นเดียวกับใน "กระบวนการซอมบี้" ใช่มั้ย ??? มีคำนิยามที่ชัดเจนของกระบวนการ "ผีดิบ" ในยูนิกซ์คือมันอธิบายกระบวนการเด็กที่มีการยกเลิก แต่ที่แม่ของกระบวนการเด็กยังคงต้อง "ปล่อย" กระบวนการเด็ก (และทรัพยากรของมัน) โดยการเรียกหรือwait waitpidกระบวนการลูกนั้นเรียกว่า "กระบวนการซอมบี้" ดูเพิ่มเติมได้ที่howtogeek.com/119815
hogliux

9
หากส่วนหนึ่งของโปรแกรมของคุณขัดข้องให้ออกจากโปรแกรมในสถานะที่ไม่ได้กำหนดแน่นอนว่าอาจทำให้เกิดปัญหากับส่วนที่เหลือของโปรแกรมของคุณ สิ่งเดียวกันอาจเกิดขึ้นได้หากคุณจัดการข้อยกเว้นอย่างไม่เหมาะสมในโปรแกรมเธรดเดี่ยว ปัญหาไม่ได้อยู่ที่เธรดปัญหาคือคุณมีสถานะที่ไม่แน่นอนในระดับโลกและคุณไม่สามารถจัดการกับการยกเลิกเธรดที่ไม่คาดคิดได้อย่างเหมาะสม เพื่อนร่วมงานที่“ สดใสจริงๆ” ของคุณนั้นไม่ได้อยู่ในกลุ่มนี้เลย
Jim Mischel

9
"นับตั้งแต่คอมพิวเตอร์เครื่องแรกมีผีอยู่ในเครื่องเสมอส่วนของรหัสแบบสุ่มที่จัดกลุ่มเข้าด้วยกันเพื่อสร้างโปรโตคอลที่ไม่คาดคิด ... "
Chris Laplante

คำตอบ:


242
  • มีคำจำกัดความที่ชัดเจนของ "เธรดซอมบี้" มากกว่าที่ฉันได้อธิบายไว้ที่นี่หรือไม่?

ดูเหมือนจะเป็นคำอธิบายที่ดีสำหรับฉัน - เธรดที่ยกเลิก (และไม่สามารถปล่อยทรัพยากรใด ๆ ได้อีกต่อไป) แต่ทรัพยากร (เช่นที่จับ) ยังคงอยู่และทำให้เกิดปัญหา

  • เธรดซอมบี้สามารถเกิดขึ้นได้บน. NET (ทำไม / เพราะเหตุใด?)
  • หากทำได้ฉันจะบังคับให้มีการสร้างเธรดซอมบี้ใน. NET ได้อย่างไร

พวกเขาแน่ใจว่าทำดูฉันทำหนึ่ง!

[DllImport("kernel32.dll")]
private static extern void ExitThread(uint dwExitCode);

static void Main(string[] args)
{
    new Thread(Target).Start();
    Console.ReadLine();
}

private static void Target()
{
    using (var file = File.Open("test.txt", FileMode.OpenOrCreate))
    {
        ExitThread(0);
    }
}

โปรแกรมนี้เริ่มหัวข้อTargetที่เปิดไฟล์แล้วฆ่าตัวเองโดยใช้ExitThreadทันที เธรด zombie ที่เป็นผลลัพธ์จะไม่ปล่อยหมายเลขอ้างอิงไปยังไฟล์ "test.txt" และดังนั้นไฟล์จะยังคงเปิดอยู่จนกว่าโปรแกรมจะสิ้นสุดลง (คุณสามารถตรวจสอบกับ process explorer หรือที่คล้ายกัน) หมายเลขอ้างอิงของ "test.txt" จะไม่ออกจนกว่าGC.Collectจะมีการเรียก - กลายเป็นว่ามันยากยิ่งกว่าที่ฉันคิดในการสร้างเธรดซอมบี้ที่รอยรั่วจับได้)

  • หากทำได้ฉันจะยกระดับการล็อคโดยไม่เสี่ยงต่อสถานการณ์ของเธรด zombie ใน. NET ได้อย่างไร

อย่าทำสิ่งที่ฉันเพิ่งทำ!

ตราบใดที่โค้ดของคุณล้างตัวเองอย่างถูกต้อง (ใช้การจัดการที่ปลอดภัยหรือคลาสที่เทียบเท่าหากทำงานกับทรัพยากรที่ไม่มีการจัดการ) และตราบใดที่คุณไม่ต้องออกนอกเส้นทางของคุณเพื่อฆ่าเธรดด้วยวิธีแปลก ๆ และมหัศจรรย์ (วิธีที่ปลอดภัยที่สุดเป็นเพียง เพื่อไม่ฆ่าเธรด - ให้พวกเขายุติตัวเองตามปกติหรือผ่านข้อยกเว้นหากจำเป็น) วิธีเดียวที่คุณจะมีบางสิ่งที่คล้ายกับเธรดซอมบี้คือถ้ามีบางสิ่งผิดปกติมาก (เช่นมีอะไรผิดพลาดใน CLR)

ในความเป็นจริงมันยากที่จะสร้างกระทู้ซอมบี้ (ฉันต้อง P / เรียกใช้ในฟังก์ชั่นที่บอกให้คุณทราบในเอกสารไม่ต้องเรียกมันนอก C) ตัวอย่างเช่นรหัส (อันยิ่งใหญ่) ต่อไปนี้ไม่ได้สร้างเธรดซอมบี้

static void Main(string[] args)
{
    var thread = new Thread(Target);
    thread.Start();
    // Ugh, never call Abort...
    thread.Abort();
    Console.ReadLine();
}

private static void Target()
{
    // Ouch, open file which isn't closed...
    var file = File.Open("test.txt", FileMode.OpenOrCreate);
    while (true)
    {
        Thread.Sleep(1);
    }
    GC.KeepAlive(file);
}

แม้จะมีข้อผิดพลาดที่น่ากลัวอยู่บ้างหมายเลขอ้างอิงของ "test.txt" ก็ยังคงถูกปิดทันทีที่Abortเรียกใช้ (ซึ่งเป็นส่วนหนึ่งของ finalizer fileที่อยู่ภายใต้ฝาครอบใช้SafeFileHandleเพื่อตัดการจัดการไฟล์)

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

ดูสิ่งนี้ด้วย


3
ฉันจำได้เมื่อฉันเล่นกับสิ่งที่บันทึกใน excel โดยใช้ backgroundworker ฉันไม่ได้ปล่อยทรัพยากรทั้งหมดตลอดเวลา (เพราะฉันเพิ่งข้ามการแก้จุดบกพร่อง ฯลฯ ) ใน taskmanager ฉันเห็นหลังจากนั้นประมาณ 50 กระบวนการ excel ฉันสร้าง zombieexcelprocesses ได้ไหม?
สเตฟาน

3
@Justin - +1 - คำตอบยอดเยี่ยม ฉันสงสัยการExitThreadโทรของคุณเล็กน้อย เห็นได้ชัดว่ามันใช้งานได้ แต่มันให้ความรู้สึกเหมือนเคล็ดลับที่ฉลาดกว่าสถานการณ์จริง หนึ่งในเป้าหมายของฉันคือการเรียนรู้สิ่งที่ไม่ควรทำเพื่อที่ฉันจะไม่ได้สร้างเธรดซอมบี้ด้วยรหัส. NET ฉันอาจจะคิดได้ว่าการเรียกรหัส C ++ ที่ทราบกันดีว่าทำให้เกิดปัญหานั้นจากรหัส. NET จะให้ผลที่ต้องการ เห็นได้ชัดว่าคุณรู้อะไรมากมายเกี่ยวกับสิ่งนี้ คุณรู้กรณีอื่น ๆ (อาจแปลก แต่ไม่แปลกพอที่จะไม่เกิดขึ้นโดยไม่ได้ตั้งใจ) กับผลลัพธ์เดียวกันหรือไม่?
smartcaveman

3
ดังนั้นคำตอบคือ 'ไม่อยู่ใน c # 4' ใช่ไหม หากคุณต้องกระโดดออกจาก CLR เพื่อรับเธรดซอมบี้ดูเหมือนว่าจะไม่เป็นปัญหา. Net
Gusdor

4
@tefan ฉันจะบอกว่าเกือบจะไม่แน่นอน ฉันทำสิ่งที่คุณอธิบายไปแล้วหลายครั้ง กระบวนการ Excel ไม่ใช่ซอมบี้เนื่องจากยังทำงานอยู่ แต่ไม่สามารถเข้าถึงได้ทันทีผ่าน UI ปกติ คุณควรจะสามารถที่จะดึงพวกเขาผ่านทางโทรไปยังGetObject Set excelInstance = GetObject(, "Excel.Application")
Daniel

7
"เธรดซอมบี้ที่เกิดขึ้นจะไม่ปล่อยหมายเลขอ้างอิงไปยังไฟล์" test.txt "และไฟล์จะยังคงเปิดอยู่จนกว่าโปรแกรมจะสิ้นสุด"ไม่ถูกต้อง หลักฐานขนาดเล็ก: `โมฆะคงที่หลัก (สตริง [] args) {กระทู้ใหม่ (GcCollect) เริ่มต้น (); กระทู้ใหม่ (เป้าหมาย). เริ่มต้น (); Console.ReadLine (); } ส่วนตัวโมฆะคงเป้าหมาย () {ใช้ (var ไฟล์ = File.Open ("test.txt", FileMode.OpenOrCreate)) {ExitThread (0); }} โมฆะคงที่ส่วนตัว GcCollect () {ขณะ (จริง) {Thread.Sleep (10,000); GC.Collect (); }} `
Sinix

46

ฉันได้คำตอบของฉันหมดแล้ว แต่ได้ทิ้งต้นฉบับไว้ด้านล่างเพื่อการอ้างอิง

เป็นครั้งแรกที่ฉันได้ยินคำว่า zombies ดังนั้นฉันจะถือว่าคำจำกัดความของมันคือ:

เธรดที่ถูกยกเลิกโดยไม่ปล่อยทรัพยากรทั้งหมด

ดังนั้นเมื่อนิยามแล้วคุณก็สามารถทำได้ใน. NET เช่นเดียวกับภาษาอื่น ๆ (C / C ++, java)

อย่างไรก็ตามฉันไม่คิดว่านี่เป็นเหตุผลที่ดีที่จะไม่เขียนเธรดรหัสภารกิจสำคัญใน. NET อาจมีเหตุผลอื่นในการตัดสินใจกับ. NET แต่การเขียน. NET เพียงเพราะคุณสามารถมีเธรดซอมบี้อย่างใดไม่เหมาะสมกับฉัน หัวข้อซอมบี้เป็นไปได้ใน C / C ++ (ฉันยังยืนยันว่ามันง่ายกว่าที่จะเลอะใน C) และแอพเธรดที่สำคัญจำนวนมากนั้นอยู่ใน C / C ++ (การซื้อขายในปริมาณมากฐานข้อมูล ฯลฯ )

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

คำตอบเดิม Zombies สามารถมีอยู่ได้ถ้าคุณไม่เขียนรหัสเธรดที่เหมาะสม เช่นเดียวกับภาษาอื่นเช่น C / C ++ และ Java แต่นี่ไม่ใช่เหตุผลที่จะไม่เขียนโค้ดเธรดใน. NET

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

รหัสที่เชื่อถือได้สำหรับระบบที่มีความสำคัญต่อภารกิจไม่ใช่เรื่องง่ายที่จะเขียนไม่ว่าคุณจะใช้ภาษาใดก็ตาม แต่ฉันคิดว่ามันเป็นไปไม่ได้ที่จะทำอย่างถูกต้องใน. NET นอกจากนี้ AFAIK,. NET เธรดก็ไม่แตกต่างจากเธรดใน C / C ++ มันใช้ (หรือสร้างจาก) การเรียกระบบเดียวกันยกเว้นการสร้างเฉพาะ. net เฉพาะบางอย่าง (เช่นเวอร์ชันน้ำหนักเบาของ RWL และคลาสเหตุการณ์)

ครั้งแรกที่ผมเคยได้ยินคำว่าซอมบี้ แต่ขึ้นอยู่กับคำอธิบายของคุณเพื่อนร่วมงานของคุณอาจจะหมายถึงหัวข้อที่สิ้นสุดโดยไม่ต้องปล่อยทรัพยากรทั้งหมด สิ่งนี้อาจทำให้เกิดการหยุดชะงักการรั่วไหลของหน่วยความจำหรือผลข้างเคียงที่ไม่ดีอื่น ๆ เห็นได้ชัดว่าไม่ได้เป็นสิ่งที่น่าพึงพอใจ แต่การแยกแยะ NET ออกมาเนื่องจากความเป็นไปได้นี้อาจไม่ใช่ความคิดที่ดีเนื่องจากเป็นไปได้ในภาษาอื่นด้วย ฉันยังยืนยันว่ามันจะเลอะง่ายใน C / C ++ มากกว่าใน. NET (โดยเฉพาะอย่างยิ่งใน C ที่คุณไม่มี RAII) แต่มีแอพสำคัญมากมายที่เขียนด้วย C / C ++ ใช่ไหม? ดังนั้นมันขึ้นอยู่กับสถานการณ์ของคุณ หากคุณต้องการที่จะดึงออนซ์ความเร็วทุกจากโปรแกรมของคุณและต้องการที่จะได้ใกล้เคียงกับโลหะเปลือยที่เป็นไปได้แล้ว .NET อาจไม่ใช่ทางออกที่ดีที่สุด หากคุณอยู่ในงบประมาณที่ จำกัด และทำการเชื่อมต่อกับเว็บเซอร์วิส /. net libraries ที่มีอยู่ / etc. NET อาจเป็นทางเลือกที่ดี


(1) ฉันไม่แน่ใจว่าจะรู้ได้อย่างไรว่าเกิดอะไรขึ้นภายใต้ประทุนเมื่อฉันกดลงexternวิธีที่ตาย หากคุณมีข้อเสนอแนะฉันชอบที่จะได้ยิน (2) ฉันยอมรับว่าเป็นไปได้ที่จะทำใน. NET ฉันอยากจะเชื่อว่ามันเป็นไปได้ด้วยการล็อค แต่ฉันยังไม่พบคำตอบที่น่าพอใจที่จะพิสูจน์ว่าวันนี้
smartcaveman

1
@ smartcaveman หากสิ่งที่คุณหมายถึงโดยlockingเป็นlockคำหลักนั้นอาจไม่ได้เนื่องจากมันเป็นอนุกรมการดำเนินการ ในการเพิ่มปริมาณงานให้มากที่สุดคุณจะต้องใช้โครงสร้างที่ถูกต้องตามคุณสมบัติของรหัสของคุณ ฉันจะบอกว่าถ้าคุณสามารถเขียนรหัสที่เชื่อถือได้ใน c / c ++ / java / อะไรก็ตามที่ใช้ pthreads / boost / thread pool / อะไรก็ตามคุณก็สามารถเขียนมันใน C # ได้เช่นกัน แต่ถ้าคุณไม่สามารถเขียนรหัสที่เชื่อถือได้ในภาษาใด ๆ โดยใช้ห้องสมุดใด ๆ ฉันสงสัยว่าการเขียนใน C # จะแตกต่างกัน
Jerahmeel

@ smartcaveman สำหรับการค้นหาว่ามีอะไรอยู่ภายใต้ประทุน Google จะช่วยให้มีจำนวนมากและหากคุณมีปัญหาในการค้นหาบนเว็บแปลกใหม่ตัวสะท้อนสัญญาณก็มีประโยชน์มาก แต่สำหรับชั้นเรียนเธรดฉันพบเอกสาร MSDN มีประโยชน์มาก และส่วนมากมันเป็นเพียงการห่อหุ้มสำหรับการเรียกระบบเดียวกับที่คุณใช้ใน C anway
Jerahmeel

1
@ smartcaveman ไม่เลยนั่นไม่ใช่สิ่งที่ฉันหมายถึง ฉันขอโทษถ้ามันเจอแบบนั้น สิ่งที่ฉันอยากจะบอกคือคุณไม่ควรเขียน. NET เร็วเกินไปเมื่อเขียนแอปพลิเคชันที่สำคัญ แน่นอนว่าคุณสามารถทำสิ่งต่าง ๆ ที่อาจเลวร้ายยิ่งกว่าเธรดซอมบี้ (ซึ่งฉันคิดว่าเป็นเพียงเธรดที่ไม่ได้ปล่อยทรัพยากรที่ไม่มีการจัดการใด ๆ ซึ่งสามารถเกิดขึ้นได้ในภาษาอื่น ๆ : stackoverflow.com/questions/14268080/ ...... ) อีกครั้งนั่นไม่ได้หมายความว่า. NET ไม่ใช่โซลูชันที่ทำงานได้
Jerahmeel

2
ฉันไม่คิดว่า. NET เป็นเทคโนโลยีที่ไม่ดีเลย จริงๆแล้วมันเป็นกรอบการพัฒนาหลักของฉัน แต่ด้วยเหตุนี้ฉันจึงคิดว่าเป็นเรื่องสำคัญที่ฉันจะต้องเข้าใจข้อบกพร่องที่มันอ่อนไหว โดยพื้นฐานแล้วฉันกำลังออกคำสั่ง. NET แต่เพราะฉันชอบมันไม่ใช่เพราะฉันไม่ได้ (และไม่ต้องกังวลเลยฉัน + คุณ 1 คน)
smartcaveman

26

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

Immortal Blue ชี้ให้เห็นว่าใน. NET 2.0 ขึ้นไปfinallyบล็อกมีภูมิคุ้มกันต่อด้ายยกเลิก และตามที่คอมเม้นต์โดย Andreas Niedermair สิ่งนี้อาจไม่ได้เป็นเธรดซอมบี้จริง แต่ตัวอย่างต่อไปนี้แสดงให้เห็นว่าการยกเลิกเธรดสามารถทำให้เกิดปัญหาได้อย่างไร:

class Program
{
    static readonly object _lock = new object();

    static void Main(string[] args)
    {
        Thread thread = new Thread(new ThreadStart(Zombie));
        thread.Start();
        Thread.Sleep(500);
        thread.Abort();

        Monitor.Enter(_lock);
        Console.WriteLine("Main entered");
        Console.ReadKey();
    }

    static void Zombie()
    {
        Monitor.Enter(_lock);
        Console.WriteLine("Zombie entered");
        Thread.Sleep(1000);
        Monitor.Exit(_lock);
        Console.WriteLine("Zombie exited");
    }
}

อย่างไรก็ตามเมื่อใช้lock() { }บล็อกfinallyจะยังคงถูกเรียกใช้เมื่อ a ThreadAbortExceptionถูกไล่ออกด้วยวิธีนั้น

ข้อมูลต่อไปนี้จะเปิดใช้งานได้เฉพาะกับ. NET 1 และ. NET 1.1 เท่านั้น:

หากภายในlock() { }บล็อกมีข้อยกเว้นอื่นเกิดขึ้นและการThreadAbortExceptionมาถึงอย่างแน่นอนเมื่อfinallyบล็อกกำลังจะถูกเรียกใช้การล็อคจะไม่ถูกปลด ดังที่คุณกล่าวถึงlock() { }บล็อกจะถูกรวบรวมเป็น:

finally 
{
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

หากมีเธรดอื่นโทรThread.Abort()เข้ามาในfinallyบล็อกที่สร้างขึ้นล็อคอาจไม่สามารถใช้งานได้


2
คุณกำลังพูดถึงlock()แต่ฉันไม่เห็นการใช้งานนี้ ... ดังนั้น - วิธีนี้เชื่อมต่อ? นี่เป็นการใช้งานที่ "ผิด" Monitor.EnterและMonitor.Exit(ขาดการใช้งานtryและfinally)
Andreas Niedermair

4
ฉันจะไม่เรียกว่า zombie-thread - นี่เป็นการใช้งานที่ผิดMonitor.EnterและMonitor.Exitไม่มีการใช้งานที่เหมาะสมtryและfinally- อย่างไรก็ตามสถานการณ์ของคุณจะล็อคเธรดอื่น ๆ ที่อาจติดอยู่_lockดังนั้นอาจมี deadlock - สถานการณ์ - ไม่จำเป็นต้องเป็น zombie thread ... นอกจากนี้คุณยังไม่ได้ปลดล็อคในMain... แต่เฮ้ ... บางที OP กำลังล็อคเพื่อหยุดยั้งแทนชุดเธรดซอมบี้ :)
Andreas Niedermair

1
@ AndreasNiedermair บางทีคำจำกัดความของเธรดซอมบี้อาจไม่ใช่สิ่งที่ฉันคิด บางทีเราสามารถเรียกสิ่งนี้ว่า "เธรดที่ยกเลิกการทำงาน แต่ไม่ได้ปล่อยทรัพยากรทั้งหมด" พยายามลบ & ทำการบ้านของฉัน แต่ยังคงรักษาคำตอบที่คล้ายคลึงกับสถานการณ์ของ OP ไว้
C.Evenhuis

2
ไม่มีความผิดที่นี่! จริง ๆ แล้วคำตอบของคุณทำให้ฉันคิดเกี่ยวกับมัน :) เนื่องจาก OP กำลังพูดถึงล็อคอย่างไม่เปิดเผยฉันเชื่อว่าเพื่อนร่วมงานของเขาพูดคุยเกี่ยวกับล็อคตาย - เพราะล็อคไม่ใช่ทรัพยากรจริงซึ่งผูกไว้กับเธรด ( มันใช้งานร่วมกัน ... nöna) - และดังนั้นจึงควรหลีกเลี่ยงการใช้เธรด "zombie" ด้วยวิธีการที่ดีที่สุดและการใช้lockหรือเหมาะสมtry/ finally-usage
Andreas Niedermair

1
@ C.Evenhuis - ฉันไม่แน่ใจว่าคำจำกัดความของฉันถูกต้อง ส่วนหนึ่งของเหตุผลที่ฉันถามคำถามคือเพื่อล้างข้อมูลนี้ ฉันคิดว่าแนวคิดนี้ถูกอ้างถึงอย่างมากใน C / C ++
smartcaveman

24

นี่ไม่ใช่เรื่องของ Zombie Zombie แต่หนังสือ Effective C # มีหัวข้อเกี่ยวกับการใช้ IDisposable (รายการที่ 17) ซึ่งพูดถึงวัตถุซอมบี้ที่ฉันคิดว่าคุณน่าสนใจ

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

มันให้ตัวอย่างคล้ายกับด้านล่าง:

internal class Zombie
{
    private static readonly List<Zombie> _undead = new List<Zombie>();

    ~Zombie()
    {
        _undead.Add(this);
    }
}

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

ตัวอย่างที่สมบูรณ์ยิ่งขึ้นอยู่ด้านล่าง เมื่อถึงเวลาที่ foreach loop คุณมีวัตถุ 150 รายการใน Undead แต่ละรายการมีภาพ แต่ภาพนั้นเป็น GC'd และคุณได้รับการยกเว้นหากคุณพยายามใช้ ในตัวอย่างนี้ฉันได้รับ ArgumentException (พารามิเตอร์ไม่ถูกต้อง) เมื่อฉันพยายามทำอะไรกับรูปภาพไม่ว่าฉันจะพยายามบันทึกหรือแม้แต่ดูมิติเช่นความสูงและความกว้าง:

class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 150; i++)
        {
            CreateImage();
        }

        GC.Collect();

        //Something to do while the GC runs
        FindPrimeNumber(1000000);

        foreach (var zombie in Zombie.Undead)
        {
            //object is still accessable, image isn't
            zombie.Image.Save(@"C:\temp\x.png");
        }

        Console.ReadLine();
    }

    //Borrowed from here
    //http://stackoverflow.com/a/13001749/969613
    public static long FindPrimeNumber(int n)
    {
        int count = 0;
        long a = 2;
        while (count < n)
        {
            long b = 2;
            int prime = 1;// to check if found a prime
            while (b * b <= a)
            {
                if (a % b == 0)
                {
                    prime = 0;
                    break;
                }
                b++;
            }
            if (prime > 0)
                count++;
            a++;
        }
        return (--a);
    }

    private static void CreateImage()
    {
        var zombie = new Zombie(new Bitmap(@"C:\temp\a.png"));
        zombie.Image.Save(@"C:\temp\b.png");
    }
}

internal class Zombie
{
    public static readonly List<Zombie> Undead = new List<Zombie>();

    public Zombie(Image image)
    {
        Image = image;
    }

    public Image Image { get; private set; }

    ~Zombie()
    {
        Undead.Add(this);
    }
}

อีกครั้งฉันรู้ว่าคุณกำลังถามเกี่ยวกับหัวข้อซอมบี้โดยเฉพาะ แต่ชื่อคำถามเกี่ยวกับซอมบี้ใน. net และฉันได้รับการเตือนเรื่องนี้และคิดว่าคนอื่นอาจคิดว่ามันน่าสนใจ!


1
สิ่งนี้น่าสนใจ มี_undeadความหมายว่าจะคงที่?
smartcaveman

1
ดังนั้นฉันจึงลองโดยพิมพ์บางส่วนจากวัตถุ "ทำลาย" มันปฏิบัติเหมือนเป็นวัตถุธรรมดา มีปัญหาในการทำเช่นนี้หรือไม่?
smartcaveman

1
ฉันได้อัปเดตคำตอบของฉันด้วยตัวอย่างซึ่งหวังว่าจะแสดงให้เห็นถึงปัญหาได้ชัดเจนขึ้น
JMK

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

4
แน่นอนว่ามันไม่ใช่IDisposableปัญหามากนัก ฉันได้รับความกังวลกับผู้เข้ารอบสุดท้าย (ผู้ทำลายล้าง) แต่การมี แต่IDisposableจะไม่ทำให้วัตถุไปที่คิวผู้เข้ารอบสุดท้ายและเสี่ยงต่อสถานการณ์ซอมบี้นี้ คำเตือนนี้เกี่ยวข้องกับผู้เข้ารอบสุดท้ายและพวกเขาอาจเรียกวิธีการกำจัด มีตัวอย่างที่IDisposableใช้ในประเภทที่ไม่มี finalizers ความเชื่อมั่นควรเป็นการล้างทรัพยากร แต่อาจเป็นการล้างทรัพยากรที่ไม่สำคัญ RX ใช้อย่างถูกต้องIDisposableสำหรับล้างข้อมูลการสมัครสมาชิกและสามารถเรียกใช้ทรัพยากรปลายน้ำอื่น ๆ (ฉันไม่ได้ลงคะแนนทั้ง btw ... )
James World

21

สำหรับระบบที่สำคัญภายใต้ภาระหนักการเขียนโค้ดที่ไม่ล็อคจะดีกว่าเป็นหลักเนื่องจากการปรับปรุงประสิทธิภาพ ดูสิ่งต่างๆเช่นLMAXและวิธีใช้ประโยชน์จาก "ความเห็นอกเห็นใจเชิงกล" เพื่อการอภิปรายที่ยอดเยี่ยมเกี่ยวกับเรื่องนี้ กังวลเกี่ยวกับเธรดซอมบี้ไหม ฉันคิดว่าเป็นกรณีที่เป็นข้อผิดพลาดที่จะรีดออกและไม่ใช่เหตุผลที่ดีพอที่จะไม่ใช้lockผมคิดว่าเป็นกรณีขอบนั่นเป็นเพียงข้อผิดพลาดที่จะรีดออกและไม่ได้เป็นเหตุผลที่ดีพอที่จะไม่ใช้

ดูเหมือนว่าเพื่อนของคุณจะเป็นคนที่มีความเพ้อฝันและแสดงความรู้เกี่ยวกับคำศัพท์ที่แปลกใหม่ให้กับฉัน! ตลอดเวลาที่ฉันใช้งานแล็บประสิทธิภาพที่ Microsoft UK ฉันไม่เคยเจอตัวอย่างของปัญหานี้ใน. NET


2
ฉันคิดว่าชุดประสบการณ์ที่แตกต่างทำให้เราหวาดระแวงเกี่ยวกับข้อบกพร่องต่าง ๆ เขายอมรับว่ามันเป็นกรณีที่ขอบที่สุดในขณะที่อธิบาย แต่ถ้าเป็นกรณีขอบจริง ๆ และไม่ใช่กรณีที่ไม่เคยอย่างน้อยฉันต้องการที่จะเข้าใจอย่างน้อยดีขึ้นเล็กน้อย - ขอบคุณสำหรับข้อมูลของคุณ
smartcaveman

1
ยุติธรรมพอสมควร ฉันแค่ไม่อยากให้คุณเป็นห่วงเรื่องlockงบ!
James World

1
ฉันจะเป็นกังวลน้อยลงถ้าฉันมีความเข้าใจในเรื่องนี้มากขึ้น
smartcaveman

4
ฉัน upvoting เนื่องจากคุณพยายามกำจัดเธรด FUD ซึ่งมีมากเกินไป Lock-FUD กำลังจะทำงานมากขึ้นเพื่อกำจัด :) ฉันเพิ่งประจบประแจงเมื่อ devs ใช้ spinlock ที่ไม่มีขอบเขต (สำหรับประสิทธิภาพ) เพื่อให้พวกเขาสามารถคัดลอกข้อมูล 50K ลงในคิวที่กว้าง
Martin James

3
ใช่ทั้งทั่วไปและเฉพาะเจาะจง การเขียนรหัสล็อคฟรีที่ถูกต้องนั้นเป็นเรื่องที่ท้าทายอย่างมาก จากประสบการณ์ของฉันในกรณีส่วนใหญ่lockบล็อกไขมันขนาดใหญ่ที่ค่อนข้างเข้าใจง่ายเป็นเรื่องที่ดี ฉันจะหลีกเลี่ยงการแนะนำความซับซ้อนเพื่อประโยชน์ในการเพิ่มประสิทธิภาพจนกว่าคุณจะรู้ว่าคุณต้องการ
James World

3

1. มีคำจำกัดความที่ชัดเจนของ "zombie thread" มากกว่าที่ฉันได้อธิบายไว้ที่นี่หรือไม่?

ฉันยอมรับว่ามี "Zombie Threads" อยู่มันเป็นคำที่อ้างถึงสิ่งที่เกิดขึ้นกับ Threads ที่เหลืออยู่กับทรัพยากรที่พวกเขาไม่ปล่อยให้ไปและยังไม่ตายอย่างสมบูรณ์ดังนั้นชื่อ "zombie" ของคุณ คำอธิบายการอ้างอิงนี้ค่อนข้างถูกต้องกับเงิน!

2.Can zombie เธรดเกิดอะไรขึ้นบน. (ทำไม / เพราะเหตุใด?)

ใช่พวกเขาสามารถเกิดขึ้นได้ เป็นข้อมูลอ้างอิงและ Windows อ้างอิงจริง ๆ ว่า "zombie": MSDN ใช้ Word "Zombie" สำหรับกระบวนการ / เธรดที่ตายแล้ว

การเกิดขึ้นบ่อยครั้งเป็นอีกเรื่องหนึ่งและขึ้นอยู่กับเทคนิคการเขียนรหัสและแนวทางปฏิบัติของคุณสำหรับคุณเช่น Thread Locking และได้ทำไปซักพักแล้วฉันจะไม่กังวลเกี่ยวกับสถานการณ์ที่เกิดขึ้นกับคุณ

และใช่ดังที่ @KevinPanko พูดถึงอย่างถูกต้องในความคิดเห็น "Zombie Threads" มาจาก Unix ซึ่งเป็นสาเหตุที่ใช้ใน XCode-ObjectiveC และเรียกว่า "NSZombie" และใช้สำหรับการดีบัก มันมีพฤติกรรมเหมือนกัน ... ความแตกต่างเพียงอย่างเดียวคือวัตถุที่ควรจะตายกลายเป็น "ZombieObject" สำหรับการดีบั๊กแทนที่จะเป็น "Zombie Thread" ซึ่งอาจเป็นปัญหาที่อาจเกิดขึ้นกับโค้ดของคุณ


1
แต่วิธีที่ MSDN ใช้zombieนั้นแตกต่างจากที่ใช้ในคำถามนี้มาก
Ben Voigt

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

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

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

0

ฉันสามารถสร้างหัวข้อซอมบี้ได้ง่ายพอ

var zombies = new List<Thread>();
while(true)
{
    var th = new Thread(()=>{});
    th.Start();
    zombies.Add(th);
}

สิ่งนี้รั่วที่จับเธรด (สำหรับ Join() ) มันเป็นเพียงแค่หน่วยความจำอื่นรั่วไหลเท่าที่เรากังวลในโลกที่มีการจัดการ

ตอนนี้การฆ่าเธรดในแบบที่มันถือล็อคจริงๆแล้วเป็นความเจ็บปวดทางด้านหลัง แต่เป็นไปได้ อีกคนหนึ่งExitThread()ทำงานได้ ในขณะที่เขาพบว่าตัวจัดการไฟล์ได้รับการทำความสะอาดโดย gc แต่lockวัตถุรอบ ๆ จะไม่เกิดขึ้น แต่ทำไมคุณถึงทำอย่างนั้น?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.