Có phải zombie tồn tại trên nền tảng .NET?


385

Tôi đã có một cuộc thảo luận với một đồng đội về việc khóa .NET. Anh ấy là một chàng trai thực sự thông minh với một nền tảng rộng lớn trong cả lập trình cấp thấp hơn và cấp cao hơn, nhưng kinh nghiệm của anh ấy với lập trình cấp thấp hơn vượt xa tôi. Dù sao, ông lập luận rằng nên tránh khóa .NET trên các hệ thống quan trọng dự kiến ​​sẽ chịu tải nặng nếu có thể để tránh khả năng nhỏ được thừa nhận là "luồng zombie" làm sập hệ thống. Tôi thường xuyên sử dụng khóa và tôi không biết "sợi zombie" là gì, vì vậy tôi đã hỏi. Ấn tượng tôi nhận được từ lời giải thích của anh ta là một chủ đề zombie là một chủ đề đã chấm dứt nhưng bằng cách nào đó vẫn giữ một số tài nguyên. Một ví dụ ông đã đưa ra về cách một luồng zombie có thể phá vỡ một hệ thống là một luồng bắt đầu một số thủ tục sau khi khóa vào một số đối tượng, và sau đó tại một số điểm chấm dứt trước khi khóa có thể được phát hành. Tình huống này có khả năng làm sập hệ thống, vì cuối cùng, các nỗ lực thực thi phương thức đó sẽ dẫn đến tất cả các luồng đang chờ truy cập vào một đối tượng sẽ không bao giờ được trả về, vì luồng đang sử dụng đối tượng bị khóa đã chết.

Tôi nghĩ rằng tôi đã nắm được ý chính của vấn đề này, nhưng nếu tôi không có cơ sở, xin vui lòng cho tôi biết. Khái niệm này có ý nghĩa với tôi. Tôi đã không hoàn toàn tin rằng đây là một kịch bản có thể xảy ra trong .NET. Trước đây tôi chưa bao giờ nghe nói về "thây ma", nhưng tôi nhận ra rằng các lập trình viên đã làm việc chuyên sâu ở các cấp thấp hơn có xu hướng hiểu sâu hơn về các nguyên tắc tính toán cơ bản (như phân luồng). Tôi chắc chắn thấy giá trị trong khóa, tuy nhiên, và tôi đã thấy nhiều lập trình viên đẳng cấp thế giới tận dụng khóa. Tôi cũng có khả năng hạn chế để đánh giá điều này cho bản thân mình vì tôi biết rằng lock(obj)tuyên bố thực sự chỉ là cú pháp cú pháp cho:

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

và bởi vì Monitor.EnterMonitor.Exitđược đánh dấu extern. Có vẻ như .NET có thể xử lý một số loại xử lý bảo vệ các luồng khỏi tiếp xúc với các thành phần hệ thống có thể gây ra tác động này, nhưng đó hoàn toàn là suy đoán và có lẽ chỉ dựa trên thực tế là tôi chưa bao giờ nghe nói về "các luồng zombie" trước. Vì vậy, tôi hy vọng tôi có thể nhận được một số phản hồi về điều này ở đây:

  1. Có một định nghĩa rõ ràng hơn về một "chủ đề zombie" so với những gì tôi đã giải thích ở đây?
  2. Chủ đề zombie có thể xảy ra trên .NET? (Tại sao tại sao không?)
  3. Nếu có thể, Làm thế nào tôi có thể buộc tạo ra một chuỗi zombie trong .NET?
  4. Nếu có thể, Làm cách nào tôi có thể tận dụng khóa mà không gặp rủi ro trong kịch bản luồng zombie trong .NET?

Cập nhật

Tôi đã hỏi câu hỏi này hơn một hai năm trước. Hôm nay điều này đã xảy ra:

Đối tượng ở trong trạng thái zombie.


8
bạn có chắc là đồng nghiệp của bạn không nói về bế tắc ??
Andreas Niedermair

10
@AndreasNiedermair - Tôi biết bế tắc là gì và rõ ràng đó không phải là vấn đề lạm dụng thuật ngữ đó. Bế tắc đã được đề cập trong cuộc trò chuyện và rõ ràng khác biệt với một "chủ đề zombie". Đối với tôi, điểm khác biệt chính là khóa chết có sự phụ thuộc hai chiều không thể giải quyết được trong khi luồng zombie là một chiều và đòi hỏi một quá trình chấm dứt. Nếu bạn không đồng ý và nghĩ rằng có một cách tốt hơn để xem xét những điều này, vui lòng giải thích
smartcaveman

19
Tôi nghĩ thuật ngữ "zombie" thực sự xuất phát từ một backgound UNIX, như trong "quá trình zombie", phải không ??? Có một định nghĩa rõ ràng về "quy trình zombie" trong UNIX: nó mô tả một quy trình con đã chấm dứt nhưng cha mẹ của quy trình con vẫn cần "giải phóng" quy trình con (và đó là tài nguyên) bằng cách gọi waithoặc waitpid. Quá trình con sau đó được gọi là "quá trình zombie". Xem thêm howtogeek.com/119815
hogliux

9
Nếu một phần chương trình của bạn gặp sự cố, khiến chương trình ở trạng thái không xác định, thì tất nhiên điều đó có thể gây ra sự cố với phần còn lại của chương trình. Điều tương tự có thể xảy ra nếu bạn xử lý không đúng các trường hợp ngoại lệ trong một chương trình đơn luồng. Vấn đề không nằm ở các luồng, vấn đề là bạn có trạng thái có thể thay đổi toàn cầu và bạn không xử lý đúng cách chấm dứt luồng không mong muốn. Đồng nghiệp "thực sự sáng" của bạn hoàn toàn không dựa trên cơ sở này.
Jim Mischel

9
"Kể từ những chiếc máy tính đầu tiên, luôn có những bóng ma trong máy. Các đoạn mã ngẫu nhiên được nhóm lại với nhau để tạo thành các giao thức bất ngờ ..."
Chris Laplante

Câu trả lời:


242
  • Có một định nghĩa rõ ràng hơn về một "chủ đề zombie" so với những gì tôi đã giải thích ở đây?

Có vẻ như một lời giải thích khá tốt cho tôi - một chủ đề đã bị chấm dứt (và do đó không còn có thể giải phóng bất kỳ tài nguyên nào), nhưng tài nguyên của họ (ví dụ như xử lý) vẫn còn xung quanh và (có khả năng) gây ra sự cố.

  • Chủ đề zombie có thể xảy ra trên .NET? (Tại sao tại sao không?)
  • Nếu có thể, Làm thế nào tôi có thể buộc tạo ra một chuỗi zombie trong .NET?

Họ chắc chắn làm, nhìn, tôi đã làm một!

[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);
    }
}

Chương trình này bắt đầu một chủ đề Targetmở một tệp và sau đó tự giết ngay lập tức bằng cách sử dụng ExitThread. Chuỗi zombie kết quả sẽ không bao giờ giải phóng tay cầm cho tệp "test.txt" và do đó tệp sẽ vẫn mở cho đến khi chương trình kết thúc (bạn có thể kiểm tra với process explorer hoặc tương tự). Việc xử lý "test.txt" sẽ không được phát hành cho đến khi GC.Collectđược gọi - hóa ra nó thậm chí còn khó khăn hơn tôi nghĩ để tạo ra một chuỗi zombie rò rỉ tay cầm)

  • Nếu có thể, Làm cách nào tôi có thể tận dụng khóa mà không gặp rủi ro trong kịch bản luồng zombie trong .NET?

Đừng làm những gì tôi vừa làm!

Miễn là mã của bạn tự dọn sạch chính xác (sử dụng Xử lý an toàn hoặc các lớp tương đương nếu làm việc với các tài nguyên không được quản lý) và miễn là bạn không tìm cách giết chết các luồng theo cách kỳ lạ và tuyệt vời (cách an toàn nhất là để không bao giờ giết chủ đề - để họ chấm dứt bản thân bình thường, hoặc thông qua ngoại lệ nếu cần thiết), cách duy nhất mà bạn sẽ phải một cái gì đó giống như một sợi zombie là nếu một cái gì đó đã đi rất sai (ví dụ như họ gặp khó khăn trong CLR).

Trên thực tế, thật khó để tạo ra một chuỗi zombie (tôi đã phải P / Gọi vào một chức năng mà thực tế sẽ cho bạn biết trong tài liệu không được gọi nó bên ngoài C). Ví dụ, mã (khủng khiếp) sau đây thực sự không tạo ra một chuỗi zombie.

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);
}

Mặc dù mắc một số lỗi khá khủng khiếp, tay cầm để "test.txt" vẫn bị đóng ngay khi Abortđược gọi (như một phần của trình hoàn thiện filemà dưới vỏ bọc sử dụng SafeFileHandle để bọc xử lý tệp của nó)

Ví dụ khóa trong câu trả lời của C.Evenhuis có lẽ là cách dễ nhất để không giải phóng tài nguyên (khóa trong trường hợp này) khi một luồng bị chấm dứt theo cách không lạ, nhưng điều đó dễ dàng được sửa bằng cách sử dụng lockcâu lệnh thay thế, hoặc đưa bản phát hành vào một finallykhối.

Xem thêm


3
tôi nhớ khi tôi chơi với việc lưu nội dung trong excel bằng trình xử lý nền, tôi đã không phát hành tất cả các tài nguyên mọi lúc (vì tôi chỉ bỏ qua việc gỡ lỗi, v.v.). trong taskmanager tôi đã thấy sau đó khoảng 50 quy trình excel. tôi đã tạo zombieexcelprocesses?
Stefan

3
@Justin - +1 - Câu trả lời tuyệt vời. Tôi có một chút hoài nghi về ExitThreadcuộc gọi của bạn mặc dù. Rõ ràng, nó hoạt động, nhưng nó cảm thấy giống như một mánh khóe thông minh hơn là một kịch bản thực tế. Một trong những mục tiêu của tôi là tìm hiểu những điều không nên làm để tôi không vô tình tạo ra các luồng zombie bằng mã .NET. Tôi có lẽ đã có thể nhận ra rằng việc gọi mã C ++ được biết là gây ra vấn đề đó từ mã .NET sẽ tạo ra hiệu ứng mong muốn. Bạn rõ ràng biết rất nhiều về công cụ này. Bạn có biết bất kỳ trường hợp nào khác (có thể kỳ lạ, nhưng không đủ kỳ lạ để không bao giờ xảy ra ngoài ý muốn) với kết quả tương tự?
smartcaveman

3
Vì vậy, câu trả lời là "không có trong c # 4", phải không? Nếu bạn phải nhảy ra khỏi CLR để lấy chuỗi zombie, thì đó không phải là vấn đề .Net.
Gusdor

4
@Stefan Tôi sẽ nói gần như chắc chắn là không. Tôi đã làm những gì bạn mô tả nhiều lần. Các quy trình Excel không phải là zombie vì chúng vẫn đang chạy mặc dù không thể truy cập ngay lập tức thông qua giao diện người dùng thông thường. Bạn sẽ có thể truy xuất chúng thông qua các cuộc gọi đến GetObject . Set excelInstance = GetObject(, "Excel.Application")
Daniel

7
"Chuỗi zombie kết quả sẽ không bao giờ giải phóng tay cầm cho tệp" test.txt "và do đó tệp sẽ vẫn mở cho đến khi chương trình kết thúc" không chính xác. Bằng chứng nhỏ: `static void Main (string [] args) {new Thread (GcCollect) .Start (); Chủ đề mới (Mục tiêu) .Start (); Bảng điều khiển.ReadLine (); } private static void Target () {using (var file = File.Open ("test.txt", FileMode.OpenOrCreate)) {ExitThread (0); }} void static void GcCollect () {while (true) {Thread.S ngủ (10000); GC.Collect (); }} `
Sinix

46

Tôi đã làm sạch câu trả lời của mình một chút, nhưng để lại câu gốc bên dưới để tham khảo

Đây là lần đầu tiên tôi nghe về thuật ngữ zombie nên tôi sẽ cho rằng định nghĩa của nó là:

Một chuỗi đã kết thúc mà không giải phóng tất cả các tài nguyên của nó

Vì vậy, với định nghĩa đó, thì có, bạn có thể làm điều đó trong .NET, như với các ngôn ngữ khác (C / C ++, java).

Tuy nhiên , tôi không nghĩ rằng đây là một lý do chính đáng để không viết mã, nhiệm vụ quan trọng trong .NET. Có thể có những lý do khác để quyết định chống lại .NET nhưng viết tắt .NET chỉ vì bạn có thể có các chủ đề zombie bằng cách nào đó không có ý nghĩa với tôi. Các luồng zombie có thể có trong C / C ++ (tôi thậm chí còn cho rằng việc lộn xộn trong C) dễ dàng hơn rất nhiều và rất nhiều ứng dụng quan trọng, có luồng trong C / C ++ (giao dịch khối lượng lớn, cơ sở dữ liệu, v.v.).

Phần kết luận Nếu bạn đang trong quá trình quyết định sử dụng ngôn ngữ, thì tôi khuyên bạn nên cân nhắc: hiệu suất, kỹ năng nhóm, lịch trình, tích hợp với các ứng dụng hiện có, v.v ... Chắc chắn, chủ đề zombie là điều bạn nên suy nghĩ , nhưng vì thật sự rất khó để mắc lỗi này trong .NET so với các ngôn ngữ khác như C, tôi nghĩ rằng mối quan tâm này sẽ bị lu mờ bởi những thứ khác như những thứ được đề cập ở trên. Chúc may mắn!

Trả lời ban đầu Zombies có thể tồn tại nếu bạn không viết mã luồng thích hợp. Điều này cũng đúng với các ngôn ngữ khác như C / C ++ và Java. Nhưng đây không phải là lý do để không viết mã luồng trong .NET.

Và cũng giống như với bất kỳ ngôn ngữ nào khác, hãy biết giá trước khi sử dụng một cái gì đó. Nó cũng giúp biết những gì đang xảy ra dưới mui xe để bạn có thể thấy trước bất kỳ vấn đề tiềm ẩn nào.

Mã đáng tin cậy cho các hệ thống quan trọng không dễ viết, dù bạn sử dụng ngôn ngữ nào. Nhưng tôi khẳng định không thể thực hiện chính xác trong .NET. Ngoài ra AFAIK, phân luồng .NET không khác với phân luồng trong C / C ++, nó sử dụng (hoặc được xây dựng từ) cùng một hệ thống gọi ngoại trừ một số cấu trúc cụ thể .net (như các phiên bản trọng lượng nhẹ của RWL và các lớp sự kiện).

lần đầu tiên tôi đã nghe nói về các zombie hạn nhưng dựa trên mô tả của bạn, đồng nghiệp của bạn có thể có nghĩa là một chủ đề mà chấm dứt mà không phát hành tất cả các nguồn lực. Điều này có khả năng gây ra bế tắc, rò rỉ bộ nhớ hoặc một số tác dụng phụ xấu khác. Điều này rõ ràng là không mong muốn nhưng chỉ ra .NET vì khả năng này có lẽ không phải là một ý tưởng tốt vì nó cũng có thể có trong các ngôn ngữ khác. Tôi thậm chí còn lập luận rằng việc gây rối trong C / C ++ dễ dàng hơn so với .NET (đặc biệt là ở C nơi bạn không có RAII) nhưng rất nhiều ứng dụng quan trọng được viết bằng C / C ++ phải không? Vì vậy, nó thực sự phụ thuộc vào hoàn cảnh cá nhân của bạn. Nếu bạn muốn trích xuất từng ounce tốc độ từ ứng dụng của mình và muốn đến gần kim loại trần nhất có thể, thì .NET có thểkhông phải là giải pháp tốt nhất Nếu bạn có ngân sách eo hẹp và giao tiếp nhiều với các dịch vụ web / thư viện .net hiện có / vv thì .NET có thể là một lựa chọn tốt.


(1) Tôi không chắc chắn làm thế nào để tìm ra những gì đang xảy ra dưới mui xe khi tôi nhấn externcác phương thức ngõ cụt . Nếu bạn có một lời đề nghị, tôi rất muốn nghe nó. (2) Tôi đồng ý có thể thực hiện bằng .NET. Tôi muốn tin rằng nó có thể với khóa, nhưng tôi vẫn chưa tìm thấy câu trả lời thỏa mãn để biện minh cho điều đó ngày hôm nay
smartcaveman

1
@smartcaveman nếu ý của bạn lockinglocktừ khóa, thì có lẽ không phải vì nó tuần tự thực hiện. Để tối đa hóa thông lượng, bạn phải sử dụng các cấu trúc phù hợp tùy thuộc vào đặc điểm của mã của bạn. Tôi muốn nói nếu bạn có thể viết mã đáng tin cậy bằng c / c ++ / java / bất cứ điều gì bằng cách sử dụng pthreads / boost / thread pool / bất cứ điều gì, thì bạn cũng có thể viết mã bằng C #. Nhưng nếu bạn không thể viết mã đáng tin cậy bằng bất kỳ ngôn ngữ nào bằng bất kỳ thư viện nào, thì tôi nghi ngờ việc viết bằng C # sẽ khác.
Jerahmeel

@smartcaveman như để tìm ra những gì dưới mui xe, google sẽ giúp rất nhiều và nếu vấn đề của bạn quá kỳ lạ để tìm thấy trên web, thì phản xạ rất tiện dụng. Nhưng đối với các lớp luồng, tôi thấy tài liệu MSDN rất hữu ích. Và rất nhiều trong số đó chỉ là các hàm bao cho cùng một cuộc gọi hệ thống mà bạn sử dụng trong C anway.
Jerahmeel

1
@smartcaveman hoàn toàn không phải vậy, đó không phải là ý tôi. Tôi xin lỗi nếu nó đi qua như vậy. Điều tôi muốn nói là bạn không nên quá nhanh chóng viết tắt .NET khi viết một ứng dụng quan trọng. Chắc chắn, bạn có thể làm những thứ có thể tệ hơn nhiều so với các luồng zombie (mà tôi nghĩ chỉ là các luồng không giải phóng bất kỳ tài nguyên không được quản lý nào, điều này hoàn toàn có thể xảy ra trong các ngôn ngữ khác: stackoverflow.com/questions/14268080/ ,), nhưng một lần nữa, điều đó không có nghĩa là .NET không phải là một giải pháp khả thi.
Jerahmeel

2
Tôi không nghĩ .NET là một công nghệ tồi cả. Đó thực sự là khung phát triển chính của tôi. Nhưng, vì điều này, tôi nghĩ điều quan trọng là tôi hiểu những sai sót mà nó dễ mắc phải. Về cơ bản, tôi đang sử dụng .NET, nhưng vì tôi thích nó chứ không phải vì tôi không thích. (Và đừng lo lắng, tôi + 1-ed bạn)
smartcaveman

26

Ngay bây giờ hầu hết các câu trả lời của tôi đã được sửa chữa bởi các ý kiến ​​dưới đây. Tôi sẽ không xóa câu trả lời vì tôi cần điểm danh tiếng vì thông tin trong các bình luận có thể có giá trị đối với độc giả.

Immortal Blue đã chỉ ra rằng trong .NET 2.0 và finallycác khối trở lên đều miễn nhiễm với việc hủy bỏ chuỗi. Và như nhận xét của Andreas Niedermair, đây có thể không phải là một chủ đề zombie thực sự, nhưng ví dụ sau đây cho thấy việc hủy bỏ một chủ đề có thể gây ra vấn đề như thế nào:

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");
    }
}

Tuy nhiên, khi sử dụng một lock() { }khối, finallyvẫn sẽ được thực thi khi mộtThreadAbortException bắn theo cách đó.

Thông tin sau đây, chỉ ra, chỉ hợp lệ cho .NET 1 và .NET 1.1:

Nếu bên trong lock() { } khối xảy ra một ngoại lệ khác và ThreadAbortExceptionđến chính xác khi finallykhối sắp chạy, khóa sẽ không được giải phóng. Như bạn đã đề cập, lock() { }khối được biên dịch là:

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

Nếu một chủ đề khác gọi Thread.Abort() bên trong finallykhối được tạo , khóa có thể không được giải phóng.


2
bạn đang nói về lock()nhưng tôi không thể thấy bất kỳ cách sử dụng này ... vì vậy - nó được kết nối như thế nào? đây là cách sử dụng "sai" Monitor.EnterMonitor.Exit(thiếu cách sử dụng tryfinally)
Andreas Niedermair

4
Tôi sẽ không gọi đó là một chủ đề zombie - đây đơn giản là việc sử dụng sai Monitor.EnterMonitor.Exitkhông sử dụng đúng cách tryfinally - dù sao, kịch bản của bạn sẽ khóa các chủ đề khác có thể bám vào _lock, do đó, có một kịch bản bế tắc - không nhất thiết là một chủ đề zombie ... Ngoài ra, bạn không phát hành khóa trong Main... nhưng, này ... có lẽ OP đang khóa vì bế tắc thay vì chủ đề zombie :)
Andreas Niedermair

1
@AndreasNiedermair có lẽ định nghĩa về một chủ đề zombie không hoàn toàn như tôi nghĩ. Có lẽ chúng ta có thể gọi đây là "luồng đã kết thúc thực thi nhưng chưa giải phóng tất cả tài nguyên". Bị cám dỗ xóa và làm bài tập về nhà của tôi, nhưng vẫn giữ câu trả lời cho giống với kịch bản của OP.
C.Evenhuis

2
không xúc phạm ở đây! Thật ra câu trả lời của bạn chỉ khiến tôi suy nghĩ về nó :) vì OP đang nói rõ ràng về việc khóa không được phát hành, tôi tin rằng đồng nghiệp của anh ấy đã nói về khóa chết - bởi vì khóa không phải là tài nguyên thực sự bị ràng buộc với một luồng ( nó được chia sẻ ... nöna) - và do đó, bất kỳ luồng "zombie" nào cũng có thể tránh được bằng cách tuân thủ các thực tiễn tốt nhất và sử dụng lockhoặc đúng cáchtry / finally-Cách sử dụng
Andreas Niedermair

1
@ C.Evenhuis - Tôi không chắc rằng định nghĩa của tôi là chính xác. Một phần lý do tại sao tôi đặt câu hỏi là để làm rõ điều này. Tôi nghĩ khái niệm này được đề cập rất nhiều trong C / C ++
smartcaveman

24

Đây không phải là về chủ đề Zombie, nhưng cuốn sách C # hiệu quả có một phần về triển khai IDis Dùng, (mục 17), nói về các đối tượng Zombie mà tôi nghĩ bạn có thể thấy thú vị.

Tôi khuyên bạn nên đọc cuốn sách này, nhưng ý chính của nó là nếu bạn có một lớp thực hiện IDis Dùng hoặc chứa Desctructor, điều duy nhất bạn nên làm trong đó là phát hành tài nguyên. Nếu bạn làm những việc khác ở đây, thì có khả năng đối tượng sẽ không được thu gom rác, nhưng cũng sẽ không thể truy cập được bằng bất kỳ cách nào.

Nó đưa ra một ví dụ tương tự như dưới đây:

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

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

Khi hàm hủy trên đối tượng này được gọi, một tham chiếu đến chính nó được đặt trong danh sách toàn cầu, nghĩa là nó tồn tại và trong bộ nhớ cho vòng đời của chương trình, nhưng không thể truy cập được. Điều này có thể có nghĩa là tài nguyên (đặc biệt là tài nguyên không được quản lý) có thể không được phát hành đầy đủ, điều này có thể gây ra tất cả các loại vấn đề tiềm ẩn.

Một ví dụ đầy đủ hơn là dưới đây. Vào thời điểm vòng lặp foreach đạt được, bạn có 150 đối tượng trong danh sách Undead, mỗi đối tượng có chứa một hình ảnh, nhưng hình ảnh đã được GC và bạn sẽ có một ngoại lệ nếu bạn cố gắng sử dụng nó. Trong ví dụ này, tôi nhận được một ArgumentException (Tham số không hợp lệ) khi tôi thử và làm bất cứ điều gì với hình ảnh, cho dù tôi có cố lưu nó hay thậm chí xem các kích thước như chiều cao và chiều rộng:

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);
    }
}

Một lần nữa, tôi biết rằng bạn đã hỏi về chủ đề zombie nói riêng, nhưng tiêu đề câu hỏi là về zombie trong .net, và tôi đã được nhắc nhở về điều này và nghĩ rằng những người khác có thể thấy nó thú vị!


1
Hay đấy. Có _undeadnghĩa là tĩnh?
smartcaveman

1
Vì vậy, tôi đã thử nó, với một số in ra từ đối tượng "bị phá hủy". Nó coi nó như một vật bình thường. Có bất cứ điều gì có vấn đề về việc này?
smartcaveman

1
Tôi đã cập nhật câu trả lời của mình bằng một ví dụ hy vọng chứng minh vấn đề rõ ràng hơn một chút.
JMK

1
Tôi không biết lý do tại sao bạn bị hạ cấp. Tôi thấy nó hữu ích. Đây là một câu hỏi - bạn sẽ nhận được loại ngoại lệ nào? Tôi không mong đợi nó sẽ là một NullReferenceException, bởi vì tôi cảm thấy điều còn thiếu cần phải gắn chặt với máy hơn là ứng dụng. Thê nay đung không?
smartcaveman

4
Chắc chắn đó không phải IDisposablelà vấn đề. Tôi có mối quan tâm với những người hoàn thiện (kẻ hủy diệt) nhưng chỉ cần IDisposablekhông làm cho một đối tượng đi đến hàng đợi quyết toán và mạo hiểm với kịch bản zombie này. Cảnh báo này liên quan đến người hoàn thiện và họ có thể gọi các phương thức Vứt bỏ. Có những ví dụ IDisposableđược sử dụng trong các loại mà không cần quyết toán. Tình cảm nên dành cho việc làm sạch tài nguyên, nhưng đó có thể là việc làm sạch tài nguyên không tầm thường. RX sử dụng hợp lệ IDisposableđể dọn sạch các đăng ký và có thể gọi các tài nguyên hạ nguồn khác. (Tôi đã không downvote hoặc btw ...)
James World

21

Trên các hệ thống quan trọng dưới tải nặng, viết mã không khóa tốt hơn chủ yếu là do các ứng dụng hiệu năng. Nhìn vào những thứ như LMAX và cách nó thúc đẩy "sự đồng cảm cơ học" cho các cuộc thảo luận tuyệt vời về điều này. Lo lắng về chủ đề zombie mặc dù? Tôi nghĩ rằng đó là một trường hợp cạnh chỉ là một lỗi cần được giải quyết, và không phải là một lý do đủ tốt để không sử dụnglock .

Âm thanh giống như bạn của bạn chỉ là lạ mắt và phô trương kiến ​​thức của mình về thuật ngữ kỳ lạ tối nghĩa với tôi! Trong tất cả thời gian tôi đang điều hành các phòng thí nghiệm hiệu suất tại Microsoft UK, tôi chưa bao giờ gặp một trường hợp nào về vấn đề này trong .NET.


2
Tôi nghĩ rằng các bộ kinh nghiệm khác nhau làm cho chúng ta hoang tưởng về các lỗi khác nhau. Ông thừa nhận nó là một trường hợp cực đoan cạnh trong khi giải thích nó, nhưng nếu nó thực sự là một lợi thế cạnh hợp cụ thể và không phải là một không bao giờ hợp cụ thể, tôi muốn ít nhất là hiểu nó tốt hơn một chút - Cảm ơn thông tin của bạn
smartcaveman

1
Đủ công bằng. Tôi chỉ không muốn bạn lo lắng một cách không tương xứng về locktuyên bố này!
Thế giới James

1
Tôi sẽ bớt lo lắng hơn nếu tôi hiểu sâu hơn về vấn đề này.
smartcaveman

4
Tôi ủng hộ vì bạn đang cố gắng loại bỏ một số FUD luồng, trong đó có quá nhiều. Lock-FUD sẽ mất nhiều công sức hơn để xử lý :) Tôi chỉ co rúm lại khi các nhà phát triển có một spinlock không giới hạn, (để thực hiện), để họ có thể sao chép 50K dữ liệu vào hàng đợi rộng.
Martin James

3
Vâng, trong cả nói chung và đặc biệt. Viết mã không khóa chính xác cũng khó khăn như lập trình. Theo kinh nghiệm của tôi đối với đại đa số các trường hợp, các khối lớn dễ hiểu (tương đối) dễ hiểu locklà tốt. Tôi sẽ tránh giới thiệu sự phức tạp vì mục đích tối ưu hóa hiệu suất cho đến khi bạn biết bạn cần.
Thế giới James

3

1. Có một định nghĩa rõ ràng hơn về một "chủ đề zombie" so với những gì tôi đã giải thích ở đây?

Tôi đồng ý rằng "Chủ đề Zombie" tồn tại, đó là một thuật ngữ để chỉ những gì xảy ra với Chủ đề còn lại với tài nguyên mà họ không bỏ đi và chưa hoàn toàn chết, do đó tên là "zombie", vì vậy bạn giải thích về giới thiệu này là khá đúng về tiền!

2.Can chủ đề zombie xảy ra trên .NET? (Tại sao tại sao không?)

Vâng, họ có thể xảy ra. Đây là một tài liệu tham khảo và thực sự được Windows gọi là "zombie": MSDN sử dụng từ "Zombie" cho các quy trình / chủ đề đã chết

Xảy ra thường xuyên là một câu chuyện khác, và phụ thuộc vào các kỹ thuật và thực hành mã hóa của bạn, như đối với bạn, giống như Khóa chủ đề và đã thực hiện nó trong một thời gian, tôi thậm chí sẽ không lo lắng về kịch bản đó xảy ra với bạn.

Và vâng, như @KevinPanko đã đề cập chính xác trong các bình luận, "Chủ đề Zombie" đến từ Unix, đó là lý do tại sao chúng được sử dụng trong XCode-ObjectiveC và được gọi là "NSZombie" và được sử dụng để gỡ lỗi. Nó hoạt động khá giống nhau ... sự khác biệt duy nhất là một vật thể đã chết trở thành "ZombieObject" để gỡ lỗi thay vì "Zombie Thread" có thể là một vấn đề tiềm ẩn trong mã của bạn.


1
Nhưng cách MSDN sử dụng zombie rất khác so với cách sử dụng câu hỏi này.
Ben Voigt

1
Ồ vâng, tôi đồng ý, nhưng tôi đã đưa ra một điểm mà ngay cả MSDN cũng đề cập đến các chủ đề là Chủ đề Zombie khi chết. và rằng trên thực tế chúng có thể xảy ra.
SH

4
Chắc chắn, nhưng nó đề cập đến một số mã khác vẫn giữ một tay cầm cho luồng, chứ không phải xử lý giữ luồng cho các tài nguyên khi nó thoát. Câu đầu tiên của bạn, đồng ý với định nghĩa trong câu hỏi, là những gì sai.
Ben Voigt

1
Ahh tôi thấy quan điểm của bạn. Thành thật mà nói tôi thậm chí không nhận thấy điều đó. Tôi đã tập trung vào quan điểm của mình rằng định nghĩa tồn tại, bất kể nó xảy ra như thế nào. Hãy nhớ rằng định nghĩa tồn tại là do những gì xảy ra với luồng chứ không phải cách nó được thực hiện.
SH

0

Tôi có thể làm cho chủ đề zombie đủ dễ dàng.

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

Điều này rò rỉ các xử lý chủ đề (cho Join() ). Nó chỉ là một rò rỉ bộ nhớ theo như chúng ta quan tâm trong thế giới được quản lý.

Bây giờ sau đó, giết một chủ đề theo cách mà nó thực sự giữ ổ khóa là một nỗi đau ở phía sau nhưng có thể. Người kia ExitThread()làm công việc đó. Như anh ta tìm thấy, phần xử lý tập tin đã được gc dọn sạch nhưng mộtlock xung quanh một đối tượng sẽ không. Nhưng tại sao bạn lại làm vậy?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.