当前位置: 首页 > 知识库问答 >
问题:

.NET中存在僵尸吗?

史鸿运
2023-03-14

我正在和一个队友讨论锁定.NET的问题。他是一个非常聪明的人,在低级和高级编程方面都有广泛的背景,但他在低级编程方面的经验远远超过我。无论如何,他认为,如果可能的话,应该避免在预期处于重载下的关键系统上进行.NET锁定,以避免“僵尸线程”使系统崩溃的可能性很小。我经常使用锁定,我不知道什么是“僵尸线程”,所以我问。我从他的解释中得到的印象是,僵尸线程是一个已经终止但不知怎么还保留着一些资源的线程。他给出的僵尸线程如何破坏系统的一个例子是,一个线程在锁定某个对象之后开始某个过程,然后在锁定可以释放之前在某个点被终止。这种情况有可能导致系统崩溃,因为最终,尝试执行该方法将导致线程都在等待对一个永远不会返回的对象的访问,因为使用锁定对象的线程已死。

我想我明白了要点,但如果我说错了,请告诉我。这个概念对我来说是有道理的。我并不完全相信这是在.NET中可能发生的真实情况。我以前从未听说过“僵尸”,但我确实认识到,在较低级别进行过深入工作的程序员往往对计算基础(如线程)有更深的理解。但是,我确实看到了锁定的价值,而且我看到了许多世界级的程序员利用锁定。我自己评估这一点的能力也很有限,因为我知道lock(obj)语句实际上只是用于:

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

而且因为monitor.entermonitor.exit标记为extern。似乎可以想象.NET会进行某种处理来保护线程免受可能产生这种影响的系统组件的影响,但这纯粹是猜测,可能只是基于我以前从未听说过“僵尸线程”这一事实。所以,我希望我能在这里得到一些反馈:

  1. “僵尸线程”的定义比我在这里解释的更清楚吗?
  2. .NET上是否会出现僵尸线程?(为什么/为什么不?)
  3. 如果适用,如何在.NET中强制创建僵尸线程?
  4. 如果适用,如何利用锁定而不冒.NET中出现僵尸线程的风险?

两年多以前我就问过这个问题。今天发生了这样的事情:

共有1个答案

徐麒
2023-03-14
  • “僵尸线程”的定义比我在这里解释的更清楚吗?

对我来说,这似乎是一个很好的解释--一个线程已经终止(因此不再释放任何资源),但其资源(例如句柄)仍然存在并(潜在地)导致问题。

  • .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杀死自己。 <罢工> 结果的僵尸线程将永远不会释放“test.txt”文件的句柄,因此该文件将保持打开状态,直到程序终止(您可以使用process explorer或类似程序进行检查)。 在调用gc.collect之前,“test.txt”的句柄不会被释放--事实证明,创建一个泄露句柄的僵尸线程比我想象的还要困难。)

  • 如果适用,如何利用锁定而不冒.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);
}

尽管犯了一些非常可怕的错误,但只要调用abort(作为file的终结器的一部分,“test.txt”的句柄仍然会关闭,该终结器使用SafeFileHandle包装其文件句柄)

c.evenhuis答案中的锁定示例可能是当线程以非怪异的方式终止时无法释放资源(在本例中为锁)的最简单的方法,但通过使用lock语句代替,或者将释放放在finally块中,可以很容易地解决这一问题。

另请参阅

  • C#IL代码的微妙之处用于一种非常微妙的情况,即即使使用lock关键字(但仅在.NET 3.5和更早版本中),异常也会阻止锁释放
  • 锁和异常不混合
 类似资料:
  • 看完这个问题:.NET中存在僵尸吗?我开始怀疑javascript代码中是否有僵尸进程发生的可能性? 有意的例子:

  • 问题内容: 维基百科说:“一个终止但从未被其父级等待的子进程变成了僵尸进程。” 我运行此程序: 这会创建一个僵尸进程,但我不明白为什么在这里创建了僵尸进程? 该程序的输出是 但是在这种情况下,为什么“子进程终止但没有被其父进程等待”呢? 问题答案: 在您的代码中,创建了僵尸(带有以下箭头的注释): 为什么?因为你从来没有上过。调用时,它将返回有关进程的事后信息,例如其退出代码。不幸的是,当进程退出

  • 僵尸进程 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。 理解了孤儿进程和僵尸进程,我们临时加了守护进程这一小节,守护进程就是后台进程吗?没那么简单。

  • 僵尸增量是一款放置游戏,你需要控制一群僵尸破坏小镇...

  • 该款植物大战僵尸是一款基于pygame的射击版防守游戏。简单易玩,很容易上手,一般射击类游戏,都可以采用该款游戏的框架。 游戏界面如下: 游戏人物介绍:              荷兰豆  玩家的代表,作用发射寒冰弹。用于阻止僵尸越过边界,攻入城堡             辐射丧尸  一种快速移动的丧尸,能够大量集结,快速突破玩家的防守      病毒丧尸 一种慢速移动的丧尸,但作为辐射丧尸的旁系

  • 问题内容: 我在前台启动了我的程序(守护程序),然后用杀死了它,但剩下一个僵尸,无法用杀死它。如何杀死僵尸进程? 如果僵尸是一个死进程(已被杀死),我如何将其从输出中删除? 问题答案: 僵尸已经死了,所以您无法杀死它。要清理僵尸,必须等待其父级等待,因此杀死父级应该可以消除僵尸。(父对象死后,僵尸将被pid 1继承,而pid 1将等待该僵尸并清除其在进程表中的条目。)如果守护程序正在生成成为僵尸的