409

I was having a discussion with a teammate about locking in .NET. He's a really bright guy with an extensive background in both lower-level and higher-level programming, but his experience with lower level programming far exceeds mine. Anyway, He argued that .NET locking should be avoided on critical systems expected to be under heavy-load if at all possible in order to avoid the admittedly small possibility of a "zombie thread" crashing a system. I routinely use locking and I didn't know what a "zombie thread" was, so I asked. The impression I got from his explanation is that a zombie thread is a thread that has terminated but somehow still holds onto some resources. An example he gave of how a zombie thread could break a system was a thread begins some procedure after locking on some object, and then is at some point terminated before the lock can be released. This situation has the potential to crash the system, because eventually, attempts to execute that method will result in the threads all waiting for access to an object that will never be returned, because the thread that is using the locked object is dead.

I think I got the gist of this, but if I'm off base, please let me know. The concept made sense to me. I wasn't completely convinced that this was a real scenario that could happen in .NET. I've never previously heard of "zombies", but I do recognize that programmers who have worked in depth at lower levels tend to have a deeper understanding of computing fundamentals (like threading). I definitely do see the value in locking, however, and I have seen many world class programmers leverage locking. I also have limited ability to evaluate this for myself because I know that the lock(obj) statement is really just syntactic sugar for:

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

and because Monitor.Enter and Monitor.Exit are marked extern. It seems conceivable that .NET does some kind of processing that protects threads from exposure to system components that could have this kind of impact, but that is purely speculative and probably just based on the fact that I've never heard of "zombie threads" before. So, I'm hoping I can get some feedback on this here:

  1. Is there a clearer definition of a "zombie thread" than what I've explained here?
  2. Can zombie threads occur on .NET? (Why/Why not?)
  3. If applicable, How could I force the creation of a zombie thread in .NET?
  4. If applicable, How can I leverage locking without risking a zombie thread scenario in .NET?

Update

I asked this question a little over two years ago. Today this happened:

Object is in a zombie state.

smartcaveman
  • 41,281
  • 29
  • 127
  • 212
  • 8
    are you sure your co-mate does not talk about deadlocking?? –  Nov 19 '13 at 08:04
  • 10
    @AndreasNiedermair - I know what deadlocking is and it was clearly not a matter of misuse of that terminology. Deadlocking was mentioned in the conversation and was clearly distinct from a "zombie thread". To me, the main distinction is that a dead lock has a two-way un-resolvable dependency whereas zombie thread is one-way, and requires a terminated process. If you disagree and think there's a better way to look at these things, please explain – smartcaveman Nov 19 '13 at 08:11
  • 2
    not necessarily ... you can also leave a lock unreleased ... which is basically the same "circular" behaviour ... but yes, you are true ... it's more a kind of starvation (http://en.wikipedia.org/wiki/Resource_starvation) :) –  Nov 19 '13 at 08:14
  • @AndreasNiedermair - I definitely recognize the similarity, but I would be hesitant to call them basically the same. Would you say that the definition of deadlocking and the four necessary conditions at http://en.wikipedia.org/wiki/Deadlock is inaccurate? – smartcaveman Nov 19 '13 at 08:17
  • @AndreasNiedermair - it definitely does seem closer to that. The example he gave me involved a locked attempt to establish a Socket connection failing - the concept itself isn't 100% clear to me (hence the question) – smartcaveman Nov 19 '13 at 08:18
  • I think it's no relationship on "Lock" or not. any threads that crashed without resource released(or released in an Abnormal way) should be called “zombie thread“. So in my option, abnormal way to release threads is the cause. – CharlieShi Nov 19 '13 at 09:23
  • @CharlieShi - examples? – smartcaveman Nov 19 '13 at 09:25
  • 20
    I think the term "zombie" actually comes from a UNIX backgound, as in "zombie process", right??? There is a clear definition of a "zombie process" in UNIX: it describes a child process that has terminated but where the parent of the child process still needs to "release" the child process (and it's resources) by calling `wait` or `waitpid`. The child process is then called a "zombie process". See also http://www.howtogeek.com/119815/ – hogliux Nov 19 '13 at 11:20
  • 2
    @hogliux - What is the correspondence between a UNIX process and a .NET process? – smartcaveman Nov 19 '13 at 11:56
  • 1
    My first thought was that it looked like a deadlock indeed, but I can see some differences by reading the answers :) – Dylan Meeus Nov 19 '13 at 13:11
  • 5
    This is abuse of the term zombie. In UNIX there is such a thing as a process that is in state `Z`. It still occupies an entry in the process table and if you have too many of them, the system will no longer be able to create more processes. In this situation with .NET, there is a thread which exits and leaves behind orphaned resources. The thread is not in state `Z`, it is simply finished and gone. (The thread will stay around in the kernel until after all handles to it are closed, in this case you may describe it as a "zombie" thread.) – Kevin Panko Nov 19 '13 at 15:10
  • 2
    Never seen one. Of all the problems that can arise in multithreaded apps, this 'zombie' thing is low down on the list. It would help a lot with such concerns if devs would stop continually creating, terminating and destroying threads. Just stop doing that, everyone, please, I beg of you! – Martin James Nov 19 '13 at 15:24
  • 9
    If part of your program crashes, leaving the program in an undefined state, then of course that can cause problems with the rest of your program. The same thing can happen if you improperly handle exceptions in a single-threaded program. The problem isn't with threads, the problem is that you have global mutable state and that you're not properly handling unexpected thread termination. Your "really bright" coworker is totally off base on this one. – Jim Mischel Nov 19 '13 at 16:23
  • 11
    "Ever since the first computers, there have always been ghosts in the machine. Random segments of code that have grouped together to form unexpected protocols..." – Chris Laplante Nov 19 '13 at 19:38
  • 3
    I think there have been enough back-and-forth edits on the title. I'm not fond of the OPs title here, but I think it best to just leave it be, please. Locking for just an hour to keep things stable for now. – Andrew Barber Nov 19 '13 at 19:40
  • 2
    I agree this is abuse of the term "zombie"; it makes it look like Unix Z processes, but it's misleading (it is clearly a different problem). Moreover, on one point he is definitely wrong: you cannot crash the system. Your process? Maybe. The system? No. If a thread terminates in a non-clean way resources are not released. But if a process terminates non-cleanly, resources DO get freed for you by the OS – Lorenzo Dematté Nov 20 '13 at 07:05
  • 1
    The title is fine; "in .NET" is a clear disambiguation that the OP is not talking about unix processes. – Izkata Nov 20 '13 at 11:53
  • 1
    We're gonna need another question "how to crash a system with a zombie thread", of course there are ways, you just gotta learn DirectX(pun intended) – Behrooz Nov 20 '13 at 16:28
  • 1
    Why is this specific to .NET? One can think of having the same issue in other languages... – joe Nov 21 '13 at 06:24
  • 3
    @joe : (1) .NET isn't a language. (2) it was specific to .NET, because the app I'm working on is .NET and my reason for asking was practical, not theoretical. This general issue is certainly applicable to some other runtime environments, but it's definitely not a universal concern for any language. There are languages where the execution environment is at such a level of abstraction that development can even be agnostic to threading altogether. As I'm learning, .NET is works at a high enough level that safety from some threading concerns (e.g. zombie threads) are greatly simplified. – smartcaveman Nov 21 '13 at 09:03
  • Answer to 4: you only need to catch ZombieThreadException to handle this problems, simple. :) Since the zombie apocalypse has not arrived yet :p – Paulo Roberto Rosa Nov 22 '13 at 09:52
  • There are already zombies in .net source code see http://referencesource.microsoft.com/#q=zombie – Maslow Aug 28 '14 at 20:52
  • 2
    Your update is a native COM object that got into a zombie state, not a .NET object. – Joshua Sep 22 '19 at 19:31

7 Answers7

250
  • Is there a clearer definition of a "zombie thread" than what I've explained here?

Seems like a pretty good explanation to me - a thread that has terminated (and can therefore no longer release any resources), but whose resources (e.g. handles) are still around and (potentially) causing problems.

  • Can zombie threads occur on .NET? (Why/Why not?)
  • If applicable, How could I force the creation of a zombie thread in .NET?

They sure do, look, I made one!

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

This program starts a thread Target which opens a file and then immediately kills itself using ExitThread. The resulting zombie thread will never release the handle to the "test.txt" file and so the file will remain open until the program terminates (you can check with process explorer or similar). The handle to "test.txt" won't be released until GC.Collect is called - it turns out it is even more difficult than I thought to create a zombie thread that leaks handles)

  • If applicable, How can I leverage locking without risking a zombie thread scenario in .NET?

Don't do what I just did!

As long as your code cleans up after itself correctly (use Safe Handles or equivalent classes if working with unmanaged resources), and as long as you don't go out of your way to kill threads in weird and wonderful ways (safest way is just to never kill threads - let them terminate themselves normally, or through exceptions if necessary), the only way that you are going to have something resembling a zombie thread is if something has gone very wrong (e.g. something goes wrong in the CLR).

In fact its actually surprisingly difficult to create a zombie thread (I had to P/Invoke into a function that esentially tells you in the documentation not to call it outside of C). For example the following (awful) code actually doesn't create a zombie thread.

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

Despite making some pretty awful mistakes, the handle to "test.txt" is still closed as soon as Abort is called (as part of the finalizer for file which under the covers uses SafeFileHandle to wrap its file handle)

The locking example in C.Evenhuis answer is probably the easiest way to fail to release a resource (a lock in this case) when a thread is terminated in a non-weird way, but thats easily fixed by either using a lock statement instead, or putting the release in a finally block.

See also

Community
  • 1
  • 1
Justin
  • 84,773
  • 49
  • 224
  • 367
  • 3
    i remember when i played with saving stuff in excel using a backgroundworker i didnt released all the resources all the time (because i just skipped debugging etc). in the taskmanager i saw afterwards about 50 excel processes. did i create zombieexcelprocesses? – Stefan Nov 19 '13 at 11:18
  • 3
    @Justin - +1 - Great answer. I'm a little skeptical of your `ExitThread` call though. Obviously, it works, but it feels more like a clever trick than a realistic scenario. One of my goals is to learn what not to do so that I don't accidentally create zombie threads with .NET code. I probably could have figured out that calling C++ code that's known to cause that issue from .NET code would produce the desired effect. You obviously know a lot about this stuff. Do you know of any other cases (possibly weird, but not weird enough to never happen unintentionally) with the same result? – smartcaveman Nov 19 '13 at 12:05
  • @Justin - the Eric Lippert article makes sense, and I have obviously read it before (there's an excerpt from the second article in my question), but I'm not sure I appreciated it as much before, so I'm going to reread it a couple times now. Can you think of any other edge cases for this that could be stumbled upon accidentally – smartcaveman Nov 19 '13 at 12:08
  • @smartcaveman You are 100% correct - `ExitThread` is definitely not a realistic scenario :) I'm not aware of any way of leaking a handle from .Net code that isn't calling P/Invoke in some way (e.g. indirectly via a 3rd party class), or a bug in the .Net framework. – Justin Nov 19 '13 at 12:10
  • @Justin - Just to clarify that I understood you correctly, would you agree that the addition of the C# `lock` keyword to any block of code can be guaranteed not to be responsible for creating a zombie thread? (And am I correct in my understanding of Lippert that the only such case for previous C# versions would result from a concurrent thread throwing a `ThreadAbortException` at exactly the right moment)? – smartcaveman Nov 19 '13 at 12:18
  • @smartcaveman Yeah I think you've understood me correctly - as long as you use the `lock` keyword instead of explicitly using `Monitor.Enter` and `Monitor.Exit` then you can be guaranteed that lock will be released when the thread terminates (unless silly P/Invokes are happening), and yes the only such case for previous C# versions is a concurrent thread calling `Thread.Abort` (as I understand it) – Justin Nov 19 '13 at 12:22
  • 4
    So the answer is 'not in c# 4', right? If you have to jump out of the CLR to get a zombie thread, it doesn't seem like a .Net problem. – Gusdor Nov 19 '13 at 12:39
  • 1
    @Justin - so, that was almost reassuring, but now I'm beginning to think there actually was something to what my teammate was saying. Here's what I'm thinking: (1) our app is on ASP.NET; (2) ASP.NET has quite a few different possible cases where it throws `ThreadAbortException`, and the execution paths that might lead to that happen on a per-request-basis; (3) under extremely heavy load, there are a lot of requests (by definition); – smartcaveman Nov 19 '13 at 12:39
  • 1
    (4) if there's also a few places where locking occurs on a per-request basis, then the probability that one thread in the thread pool is exiting the lock at the same time that another thread is doing some throwing a `ThreadAbortException` from something like a `Response.End()` is not so small as to be insignificant. What do you think? – smartcaveman Nov 19 '13 at 12:40
  • 1
    @smartcaveman The `ThreadAbortExceptions` that ASP.Net throws are are thrown by the current thread, so I think the race condition while entering a lock that Eric Lippert is talking about isn't applicable (and also can't happen if the code is compiled with optimizations anyway). Its only if another thread causes the `ThreadAbortException` to be raised that the exception could be thrown in the no-op before the try – Justin Nov 19 '13 at 12:47
  • 1
    @Justin - Not sure I'm following. In ASP.NET you have thread pools, so let's say there are 10 threads (thread#1, thread#2...thread#10). let's say at the same time thread#3 is about to enter a locked context to load some expensive query with a `Lazy>` and right before (in that no-op space) thread#7 calls `Response.End()`. Why wouldn't that qualify? – smartcaveman Nov 19 '13 at 13:09
  • 4
    @Stefan I would say almost definitely not. I've done what you described plenty of times. The Excel processes are not zombies as they are still running though not immediately accessible via the normal UI. You should be able to retrieve them via calls to [GetObject](http://office.microsoft.com/en-us/excel-help/HV080557182.aspx). `Set excelInstance = GetObject(, "Excel.Application")` – Daniel Nov 19 '13 at 13:55
  • @smartcaveman Because `Response.End` would only be aborting thread#7 (the current thread that `Response.End` is called on). thread#3 will continue running (and enter the locked context). If thread#3 is being interrupted then the only thing I can think of would be tha there might be something in your codebase which is calling abort on other threads when a response is ended. – Justin Nov 19 '13 at 13:59
  • Hmm... why the lack of the `using` statement or discussing when it should be used? – Powerlord Nov 20 '13 at 01:13
  • *"the handle to "test.txt" is still closed as soon as `Abort()` is called, as part of the finalizer for `file`..."* - But the finalizer for `file` isn't guaranteed to be called during `Abort()`, or at all for that matter... – BlueRaja - Danny Pflughoeft Nov 20 '13 at 01:53
  • 7
    _"The resulting zombie thread will never release the handle to the "test.txt" file and so the file will remain open until the program terminates"_ is incorrect. Small proof: ` static void Main(string[] args) { new Thread(GcCollect).Start(); new Thread(Target).Start(); Console.ReadLine(); } private static void Target() { using (var file = File.Open("test.txt", FileMode.OpenOrCreate)) { ExitThread(0); } } private static void GcCollect() { while (true) { Thread.Sleep(10000); GC.Collect(); } }` – Sinix Nov 20 '13 at 07:14
  • @Justin - I think I see what you're saying. I think we interpreted this line from Lippert's article differently: "it is possible for another thread to cause a thread abort exception while the thread that just took the lock is in the no-op". I took it to mean that something caused another executing thread to throw a `ThreadAbortException` for some unrelated reason and that this would somehow cause the thread that took the lock to never release the lock. – smartcaveman Nov 20 '13 at 17:06
  • @Justin, The way I believe you interpreted it is that the second thread would half a reference to the thread that took the lock and would call `thread.Abort()` on the thread with the lock while the thread with the lock was in no-op. This makes a whole lot more sense now that I think about it. And, it seems consistent with both the rest of the article and your answers to my questions about ASP.NET. Let me know if we're finally on the same page. – smartcaveman Nov 20 '13 at 17:07
  • I'm going to go out on a limb and say - good on you for trying, but you still failed to create a zombie thread, because it's not possible. If you took a memory dump from your example(s) and looked at the running threads you would find the thread was not there; you would find the thread *object* in the heap because a live reference is still active due to the gcroot in your `main()`, but the OS thread would be gone. Like all other answers here - you're failing to mention the simple truth that this conceptually doesn't make sense and can't happen in .NET. – Jimmy Hoffa Oct 27 '15 at 17:34
  • @smartcaveman Since the ASP.NET thread pool is recycled when the app is "idle" (no HTTP requests for ~20 min or so), which `Abort()`s all threads, I'm now worried that you can potentially run into a lot of issues with this when doing things (without a HTTP request) in a background task... – AyCe Jul 17 '19 at 16:32
  • Problem with that explanation: resources such as file handles belong to the process, not the thread (I can't think of any that belong to a thread and aren't released automatically when the thread terminates). So if "zombie thread" is to say: *that thread was supposed to release a resource, but didn't, so the resource is still there*, then you don't have a zombie thread, you have a resource leak. – peterchen Mar 18 '20 at 08:53
  • @Justin The url to "Locks and exceptions do not mix" is no longer valid. – Nick Jun 22 '22 at 23:09
47

I've cleaned up my answer a bit, but left the original one below for reference

It’s the first time I've heard of the term zombies so I'll assume its definition is:

A thread that has terminated without releasing all of its resources

So given that definition, then yes, you can do that in .NET, as with other languages (C/C++, java).

However, I do not think this as a good reason not to write threaded, mission critical code in .NET. There may be other reasons to decide against .NET but writing off .NET just because you can have zombie threads somehow doesn't make sense to me. Zombie threads are possible in C/C++ (I'd even argue that it’s a lot easier to mess up in C) and a lot of critical, threaded apps are in C/C++ (high volume trading, databases etc).

Conclusion If you are in the process of deciding on a language to use, then I suggest you take the big picture into consideration: performance, team skills, schedule, integration with existing apps etc. Sure, zombie threads are something that you should think about, but since it’s so difficult to actually make this mistake in .NET compared to other languages like C, I think this concern will be overshadowed by other things like the ones mentioned above. Good luck!

Original Answer Zombies can exist if you don't write proper threading code. The same is true for other languages like C/C++ and Java. But this is not a reason not to write threaded code in .NET.

And just like with any other language, know the price before using something. It also helps to know what is happening under the hood so you can foresee any potential problems.

Reliable code for mission critical systems is not easy to write, whatever language you're in. But I'm positive it’s not impossible to do correctly in .NET. Also AFAIK, .NET threading is not that different from threading in C/C++, it uses (or is built from) the same system calls except for some .net specific constructs (like the light weight versions of RWL and event classes).

first time I've heard of the term zombies but based on your description, your colleague probably meant a thread that terminated without release all resources. This could potentially cause a deadlock, memory leak or some other bad side effect. This is obviously not desirable but singling out .NET because of this possibility is probably not a good idea since it’s possible in other languages too. I'd even argue that it’s easier to mess up in C/C++ than in .NET (especially so in C where you don't have RAII) but a lot of critical apps are written in C/C++ right? So it really depends on your individual circumstances. If you want to extract every ounce of speed from your application and want to get as close to bare metal as possible, then .NET might not be the best solution. If you are on a tight budget and do a lot of interfacing with web services/existing .net libraries/etc then .NET may be a good choice.

binki
  • 7,754
  • 5
  • 64
  • 110
Jerahmeel
  • 620
  • 4
  • 11
  • (1) I'm not sure how to figure out what's happening under the hood when I hit dead end `extern` methods. If you've got a suggestion, I'd love to hear it. (2) I would agree it's possible to do in .NET. I would like to believe it's possible with locking, but I have yet to find a satisfying answer to justify that today – smartcaveman Nov 19 '13 at 09:06
  • 1
    @smartcaveman if what you mean by `locking` is the `lock` keyword, then probably not since it serializes execution. To maximize throughput, you'd have to use the right constructs depending on the characteristics of your code. I'd say if you can write reliable code in c/c++/java/whatever using pthreads/boost/thread pools/whatever, then you can write it in C# too. But if you can't write reliable code in any language using any library, then I doubt writing in C# will be any different. – Jerahmeel Nov 19 '13 at 09:18
  • @smartcaveman as for figuring out what's under the hood, google helps heaps and if you're issue is too exotic to find on the web, reflector is very handy. But for the threading classes, I find the MSDN documentation very useful. And a lot of it are just wrappers for the same system calls you use in C anway. – Jerahmeel Nov 19 '13 at 09:21
  • Jerahmeel, (1) If you can find something on Google that makes this look like a LMGTFY question, I will PayPal you $100 immediately. Otherwise, please be fair. (2) You can't use reflector on `extern` methods. (3) I don't know C. Can you recommend a good book/resource targeting strong programmers who haven't worked with C before? – smartcaveman Nov 19 '13 at 09:29
  • 1
    @smartcaveman not at all, that's not what I meant. I'm sorry if it came across that way. What I wanted to say is that you shouldn't be too quick to write off .NET when writing a critical application. Sure, you could do stuff that maybe a lot worse than zombie threads (which I think are just threads that didn't release any unmanaged resources, which can totally happen in other languages: http://stackoverflow.com/questions/14268080/cancelling-a-thread-that-has-a-mutex-locked-does-not-unlock-the-mutex), but again, that doesn't mean that .NET is not a viable solution. – Jerahmeel Nov 19 '13 at 11:11
  • 2
    I don't think .NET is a bad technology at all. It's actually my primary development framework. But, because of this, I think it's important that I understand the flaws to which it's susceptible. Basically, I am singling out .NET, but because I like it, not because I don't. (And no worries bro, I +1-ed you) – smartcaveman Nov 19 '13 at 12:22
  • It is a reason not to write threaded code in .NET, but not a reason not to use .NET. Instead of threaded code and explicit locks, use async Tasks, which also gives overlapping and parallelism but in a way that's much easier to reason about and get correct. Or any other message-passing architecture. If any piece of data is owned by only one task at a time, there's no need for locking (except in the task queuing code, which you don't have to write). – Ben Voigt Nov 20 '13 at 16:16
  • Certainly. In CPU intensive code, multi-threaded applications at the very least free up the UI thread to maintain responsiveness to the user and allow the user to perform multiple concorrent tasks. – WonderWorker Nov 24 '15 at 13:13
27

Right now most of my answer has been corrected by the comments below. I won't delete the answer because I need the reputation points because the information in the comments may be valuable to readers.

Immortal Blue pointed out that in .NET 2.0 and up finally blocks are immune to thread aborts. And as commented by Andreas Niedermair, this may not be an actual zombie thread, but the following example shows how aborting a thread can cause problems:

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

However when using a lock() { } block, the finally would still be executed when a ThreadAbortException is fired that way.

The following information, as it turns out, is only valid for .NET 1 and .NET 1.1:

If inside the lock() { } block an other exception occurs, and the ThreadAbortException arrives exactly when the finally block is about to be ran, the lock is not released. As you mentioned, the lock() { } block is compiled as:

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

If another thread calls Thread.Abort() inside the generated finally block, the lock may not be released.

C.Evenhuis
  • 25,996
  • 2
  • 58
  • 72
  • 2
    you are talking about `lock()` but i cannot see any usage of this ... so - how is this connected? this is a "wrong" usage of `Monitor.Enter` and `Monitor.Exit` (lacking the usage of `try` and `finally`) –  Nov 19 '13 at 07:59
  • @AndreasNiedermair the example should answer question 2 and 3, I'll update the answer. – C.Evenhuis Nov 19 '13 at 07:59
  • 4
    I would not call that a zombie-thread - this is simply a wrong usage of `Monitor.Enter` and `Monitor.Exit` without proper usage of `try` and `finally` - anyway, your scenario will lock other threads which might hang on to `_lock`, so there's a deadlock-scenario - not necessarily a zombie thread ... Also, you are not releasing the lock in `Main` ... but, hey ... maybe the OP is locking for deadlocking instead of zombie-threads :) –  Nov 19 '13 at 08:01
  • 1
    @AndreasNiedermair perhaps the definition of a zombie thread isn't quite what I thought. Perhaps we can call this a "thread that has terminated execution but has not released all resources". Tempted to delete & do my homework, but keeping the answer for the resemblance to the OP's scenario. – C.Evenhuis Nov 19 '13 at 08:05
  • 2
    no offence here! actually your answer just got me thinking about it :) as the OP is explicitely talking about locks not being released, i believe his co-mate talked about dead-locking - because a lock is no real-resource which is bound to a thread (it's shared... nöna) - and therefore any "zombie" thread could be avoided by sticking to best-practices and using `lock` or proper `try`/`finally`-usage –  Nov 19 '13 at 08:09
  • @AndreasNiedermair that, _and_ avoiding `Thread.Abort()`. No offense taken; I went with the OP's definition of zombie without backing it up. – C.Evenhuis Nov 19 '13 at 08:15
  • i must correct ... starvation :) (http://en.wikipedia.org/wiki/Resource_starvation) –  Nov 19 '13 at 08:16
  • 1
    @C.Evenhuis - I'm not sure that my definition is accurate. Part of why I'm asking the question is to clear this up. I think the concept is referred to heavily in C/C++ – smartcaveman Nov 19 '13 at 08:19
  • @C.Evenhuis - I'm having a little trouble following exactly what you mean when you're talking about the exception getting thrown inside the lock. The general idea is clear, but I'm not necessarily getting it in context of the other code you wrote, nor am I totally sure if you're referring to an actual `lock` statement, or to the `Monitor.Enter` / `Monitor.Exit` code that you wrote. I'm thinking you meant the `lock` statement, because of the note about `finally` but please confirm. – smartcaveman Nov 19 '13 at 12:31
  • @smartcaveman see my updated answer. I was indeed talking about talking about the possibility of the `finally` block not being executed. – C.Evenhuis Nov 19 '13 at 12:36
  • 1
    @C.Evenhuis finally blocks are immune from thread abort exceptions - see http://social.msdn.microsoft.com/Forums/en-US/9e8f0423-dde5-4687-ad52-6d442d83d316/threadabort-fixed-for-finally-blocks – Immortal Blue Nov 19 '13 at 12:56
  • @ImmortalBlue thanks for pointing that out. My information is very outdated: "In the .NET Framework versions 1.0 and 1.1, there is a chance the thread could abort while a finally block is running, in which case the finally block is aborted." http://msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.110).aspx – C.Evenhuis Nov 19 '13 at 15:01
  • Instead of leaving an incorrect answer unchanged due valuable information in the comments, please consider updating/correcting it instead. Despite having look at the comments note at the start, the rest of your answer has much higher visibility than the comments do. Alternately, if the information is available in other answers keeping a second copy of it here isn't needed. – Dan Is Fiddling By Firelight Nov 20 '13 at 16:46
  • The fact that a lock would get released by a thread abort is often *not* a good thing. Suppose an object has two fields that need to be updated consistently, and Zombie acquires a lock, updates one field, updates the other field, and then releases the lock. Should another thread be able to acquire the lock while the fields hold inconsistent values? – supercat Jan 21 '20 at 22:38
24

This isn't about Zombie threads, but the book Effective C# has a section on implementing IDisposable, (item 17), which talks about Zombie objects which I thought you may find interesting.

I recommend reading the book itself, but the gist of it is that if you have a class either implementing IDisposable, or containing a Desctructor, the only thing you should be doing in either is releasing resources. If you do other things here, then there is a chance that the object will not be garbage collected, but will also not be accessible in any way.

It gives an example similar to below:

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

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

When the destructor on this object is called, a reference to itself is placed on the global list, meaning it stays alive and in memory for the life of the program, but isn't accessible. This may mean that resources (particularly unmanaged resources) may not be fully released, which can cause all sorts of potential issues.

A more complete example is below. By the time the foreach loop is reached, you have 150 objects in the Undead list each containing an image, but the image has been GC'd and you get an exception if you try to use it. In this example, I am getting an ArgumentException (Parameter is not valid) when I try and do anything with the image, whether I try to save it, or even view dimensions such as height and width:

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

Again, I am aware you were asking about zombie threads in particular, but the question title is about zombies in .net, and I was reminded of this and thought others may find it interesting!

JMK
  • 27,273
  • 52
  • 163
  • 280
  • 1
    This is interesting. Is `_undead` meant to be static? – smartcaveman Nov 19 '13 at 09:52
  • 1
    So I tried it, with some printing out of the "destructed" object. It treats it like a normal object. Is there anything problematic about doing this? – smartcaveman Nov 19 '13 at 09:57
  • 1
    I've updated my answer with an example which hopefully demonstrates the issue a bit more clearly. – JMK Nov 19 '13 at 10:02
  • 1
    I don't know why you got downvoted. I found it helpful. Here's a question - what kind of exception are you going to get? I don't expect it would be a `NullReferenceException`, because I get the feeling the missing thing needs to be more tied to the machine than to the application. Is this right? – smartcaveman Nov 19 '13 at 12:28
  • 1
    Well thanks very much, added the exception I get to the answer, although I don't fully understand why I get that particular exception! – JMK Nov 19 '13 at 12:46
  • 4
    Surely it's not so much `IDisposable` that's the issue. I get the concern with finalizers (destructors) but just having `IDisposable` won't make an object go to the finalizer queue and risk this zombie scenario. This warning concerns finalizers, and they might call Dispose methods. There are examples where `IDisposable` is used in types without finalizers. The sentiment should be for resource clean-up, but that could be non-trivial resource clean-up. RX validly uses `IDisposable` for clean up of subscriptions and can call other downstream resources. (I didn't downvote either btw...) – James World Nov 19 '13 at 14:14
  • 1
    The problem with doing anything but releasing native resources in a finalizer is that any referenced objects may have already been finalized themselves. Avoiding this would require searching all "unreachable"(from any "root") objects for references to them from any _other_ "unreachable" objects, and there might be cycles anyway. This applies even inside a finalizer, not just if you try to "resurrect" an object by adding a new reference to it; though doing so helps demonstrate "loosing" the race condition. Hmm, 6 years later so this is necromancy on a thread about zombies. – SensorSmith Dec 31 '19 at 23:07
21

On critical systems under heavy load, writing lock-free code is better primarily because of the performance improvments. Look at stuff like LMAX and how it leverages "mechanical sympathy" for great discussions of this. Worry about zombie threads though? I think that's an edge case that's just a bug to be ironed out, and not a good enough reason not to use lock.

Sounds more like your friend is just being fancy and flaunting his knowledege of obscure exotic terminology to me! In all the time I was running the performance labs at Microsoft UK, I never came across an instance of this issue in .NET.

James World
  • 29,019
  • 9
  • 86
  • 120
  • 2
    I think different sets of experiences make us paranoid about different bugs. He acknowledged it's an extreme edge case while explaining it, but if it really is an edge-case and not a never-case, I'd like to at least understand it a little better - Thanks for your input – smartcaveman Nov 19 '13 at 08:27
  • 1
    Fair enough. I just wouldn't want you to be disproportionately worried about the `lock` statement! – James World Nov 19 '13 at 08:49
  • 1
    I'd be much less worried if I had a deeper understanding of this issue. – smartcaveman Nov 19 '13 at 09:04
  • 4
    I upvoted because you are trying to get rid of some thread-FUD, of which there is far too much. Lock-FUD is going to take more work to dispose of:) I just cringe when devs take an unbounded spinlock, (for performance), so that they can copy 50K of data onto a wide queue. – Martin James Nov 19 '13 at 15:47
  • 1
    By performance improvements, do you mean the actual cost of locking? Also, are you speaking about locks in .NET or locks in general? – smartcaveman Nov 20 '13 at 21:26
  • 3
    Yes, in both the general and particular. Writing correct lock-free code is about as challenging as programming gets. In my experience for the vast majority of cases, big fat course-grained (relatively) easy to comprehend `lock` blocks are just fine. I would at avoid introducing complexity for the sake of performance optimization until you know you need to. – James World Nov 20 '13 at 22:22
  • 1
    +100 if I could. Spot on about correct lock-free code, I've had to optimize some really critical sections of code before for this particular purpose in high-traffic IIS systems; I can think of nothing more complex or strange I've done. On the plus side, that experience taught me a significant amount about how multithreading and resources work in .NET to which I say: Bravo for being the only sane voice here. Every other answer here is really stretching and confusing issues because these are not well known/studied things. People need to read Tess Fernandez' blog or avoid commenting on GC. – Jimmy Hoffa Oct 27 '15 at 17:30
3

1.Is there a clearer definition of a "zombie thread" than what I've explained here?

I do agree that "Zombie Threads" exist, it's a term to refer to what happens with Threads that are left with resources that they don't let go of and yet don't completely die, hence the name "zombie," so your explanation of this referral is pretty right on the money!

2.Can zombie threads occur on .NET? (Why/Why not?)

Yes they can occur. It's a reference, and actually referred to by Windows as "zombie": MSDN uses the Word "Zombie" for Dead processes/threads

Happening frequently it's another story, and depends on your coding techniques and practices, as for you that like Thread Locking and have done it for a while I wouldn't even worry about that scenario happening to you.

And Yes, as @KevinPanko correctly mentioned in the comments, "Zombie Threads" do come from Unix which is why they are used in XCode-ObjectiveC and referred to as "NSZombie" and used for debugging. It behaves pretty much the same way... the only difference is an object that should've died becomes a "ZombieObject" for debugging instead of the "Zombie Thread" which might be a potential problem in your code.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
S.H.
  • 2,833
  • 3
  • 26
  • 28
  • 1
    But the way MSDN uses *zombie* is very different from the way this question is using it. – Ben Voigt Nov 20 '13 at 16:14
  • 1
    Oh yes I agree, but I was making a point that even MSDN refers to threads as Zombie Threads when dead. and that they in fact can occur. – S.H. Nov 20 '13 at 16:29
  • 4
    Sure, but it's referring to some other code still holding a handle to the thread, not the thread holding handles to resources when it exited. Your first sentence, agreeing with the definition in the question, is what's wrong. – Ben Voigt Nov 20 '13 at 16:30
  • 1
    Ahh I see your point. To be honest I didn't even notice that. I was focusing to his point that the definition exists, regardless of how it happens. Remember that the definition exist because of what happens to the thread, and not how it is done. – S.H. Nov 20 '13 at 16:35
0

I can make zombie threads easily enough.

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

This leaks the thread handles (for Join()). It's just another memory leak as far as we are concerned in the managed world.

Now then, killing a thread in a way that it actually holds locks is a pain in the rear but possible. The other guy's ExitThread() does the job. As he found, the file handle got cleaned up by the gc but a lock around an object wouldn't. But why would you do that?

Joshua
  • 40,822
  • 8
  • 72
  • 132