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); }
因为班长。进入并监控。出口被标记为extern。似乎可以想象,. net做了某种处理来保护线程不暴露给可能产生这种影响的系统组件,但这纯粹是推测,可能只是基于我以前从未听说过“僵尸线程”这一事实。所以,我希望我能在这里得到一些反馈:
有没有比我在这里解释的更明确的“僵尸线程”定义呢?
僵尸线程会在。net上出现吗?(为什么/为什么不?)
如果适用,我如何在。net中强制创建僵尸线程?
如果适用,我如何在。net中利用锁定而不冒僵尸线程场景的风险?
更新
我在两年多以前问过这个问题。今天发生了这样的事:
现在我的大部分回答都被下面的评论更正了。我不会删除答案,因为我需要声誉点,因为评论中的信息可能对读者有价值。
不朽之蓝指出,在。net 2.0及更高版本中,最终块对线程中止免疫。正如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(){}块时,当ThreadAbortException以这种方式触发时,finally仍然会被执行。
下面的信息只对。net 1和。net 1.1有效:
如果在lock(){}块内部发生了其他异常,并且ThreadAbortException恰好在finally块即将运行时到达,则不会释放锁。正如你提到的,lock(){}块被编译为:
finally
{
if (lockWasTaken)
Monitor.Exit(temp);
}
如果另一个线程在生成的finally块中调用thread . abort(),锁可能不会被释放。
我把我的答案整理了一下,但把原来的答案留在下面供参考
这是我第一次听说僵尸这个词,所以我假设它的定义是:
未释放其所有资源而终止的线程
因此,根据这个定义,是的,你可以在。net中这样做,就像其他语言(C/ c++, java)一样。
然而,我不认为这是一个不使用。net编写线程化的关键任务代码的好理由。可能有其他原因决定不使用。net,但仅仅因为你可以有僵尸线程就放弃。net对我来说没有意义。僵尸线程在C/ c++中是可能的(我甚至认为在C中更容易搞砸),许多关键的线程应用程序在C/ c++中(大量交易,数据库等)。
结论
如果你正在决定使用一种语言,那么我建议你从大局考虑:性能、团队技能、进度、与现有应用的集成等。当然,僵尸线程是你应该考虑的事情,但是由于与C等其他语言相比,在。net中很难犯这个错误,我认为这个问题会被上面提到的其他问题所掩盖。好运!
原来的答案
僵尸†可以存在,如果你不写适当的线程代码。对于C/ c++和Java等其他语言也是如此。但这并不是不使用。net编写线程代码的理由。
就像使用其他语言一样,在使用某些东西之前要知道价格。它还有助于了解底层发生了什么,从而可以预见任何潜在的问题。
为关键任务系统编写可靠的代码并不容易,无论您使用何种语言。但我确信在。net中也不是不可能做到。另外,.net线程与C/ c++中的线程并没有什么不同,它使用(或由)相同的系统调用,除了一些.net特定的结构(如RWL和事件类的轻量级版本)。
†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.
有没有比我在这里解释的更明确的“僵尸线程”定义呢?
对我来说似乎是一个很好的解释-一个线程已经终止(因此不能再释放任何资源),但其资源(例如句柄)仍然存在并(潜在地)导致问题。
僵尸线程会在。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);
}
}
这个程序启动一个线程目标,它打开一个文件,然后立即使用ExitThread杀死自己。由此产生的僵尸线程将永远不会释放“test.txt”文件的句柄,因此该文件将保持打开状态,直到程序终止(您可以使用进程资源管理器或类似工具进行检查)。“test.txt”的句柄直到GC才会被释放。收集被称为-事实证明,甚至比我想象的更难创建一个僵尸线程泄漏处理)
如果适用,我如何在。net中利用锁定而不冒僵尸线程场景的风险?
别做我刚才做的事!
只要正确代码运行后会自己清除(使用安全处理或等价类如果使用非托管资源),只要你不离开你的日常路径以怪异而精彩的方式杀死线程(线程安全的方法就是从来没有杀死,让他们正常终止自己,或通过例外如果必要的话),只有这样,你会有类似于僵尸线程如果已经非常错误的(如出现问题在CLR)。
事实上,创建一个僵尸线程非常困难(我不得不P/Invoke到一个函数,基本上在文档中告诉你不要在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被调用时关闭(作为文件终结器的一部分,它在隐藏中使用SafeFileHandle来包装它的文件句柄)
C.Evenhuis中的锁定示例可能是在线程以正常方式终止时无法释放资源(在这种情况下是锁)的最简单方法,但这很容易通过使用lock语句或将释放放在finally块中来修复。
另请参阅
c# IL的微妙之处
codegen
这是一个非常微妙的情况,异常可以阻止锁从
即使使用lock关键字也会被释放(但仅限于。net 3.5及更早版本)
锁和异常则不然
混合