CSharp - .NET存在僵尸?

  显示原文与译文双语对照的内容

我与一位队友讨论了在. NET 中锁定。 他是一个非常聪明的人,在lower-level和高级编程中有着广泛的背景,但他的低级编程经验远远超过了我的经验。 不管怎么样,他争辩说,.NET 锁定应该避免在关键系统预计将在heavy-load如果可能的话为了避免了固然操作也可能造成"丧尸螺纹"导致崩溃的一个系统。 我经常使用锁定,但不知道"丧尸螺纹"是什么,所以我问。 我从他的解释中得到的印象是,僵尸线程是一个已经终止但仍在某些资源上的线程。 他给出的一个僵尸线程如何中断系统的例子是线程在锁定某个对象后开始一些过程,然后在释放锁之前终止。 这种情况可能导致系统崩溃,因为最终尝试执行该方法会导致线程都等待访问永远不会返回的对象,因为正在使用锁定对象的线程死了。

我想我得到了这一点的要点,但是如果我离开了基地,请让我知道。 这个概念对我有意义。 我并不是完全相信这是在. 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? 线程场景的风险
时间:

现在我的大部分答案都被下面的评论纠正了。 我不会删除答案 因为我需要信誉点 因为评论中的信息对读者很有价值。

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

然而当使用在 finallylock() { } 块,可能仍然是一个 ThreadAbortException 触发之后那个方向执行。

下面的信息只对. NET 1和. NET 1.1有效:

如果出现在其他异常的lock() { } 块内,且该 ThreadAbortException 到达的话也是在那天 finally 块即将被运行了,锁没有释放。 就像你提到的,lock() { } 块编译为:


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

如果一个线程调用生成 finallyThread.Abort() 内的块,则可能不会释放锁。

这并不是关于僵尸线程,但是图书有效的C# 有一个关于实现 IDisposable,( 项目 17 )的章节,它谈到了我认为你可以找到的僵尸对象。

我建议自己阅读本书,但要点是如果你有一个类实现 IDisposable,或者包含一个 Desctructor,你应该做的就是释放资源。 如果你在这里做其他事情,那么对象就不会被垃圾收集,但也不会以任何方式被访问。

它给出了一个类似下面的示例:


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

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

当调用这里对象上的析构函数时,会在全局 List 上放置一个引用,这意味着它在程序的生存期内处于活动状态和内存中,但不可访问。 这可能意味着资源( 特别是非托管资源) 可能没有完全释放,这可能导致各种潜在的问题。

一个更完整的例子如下。 通过达到的时间并不限于只时,你拥有 150亡灵 List 每个都包含一个图像,但图像中的对象已经被垃圾回收原本可能会产生一个异常如果你尝试使用它。 在本例中,当我尝试使用图像进行任何操作时,我将得到一个 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:tempx.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:tempa.png"));
 zombie.Image.Save(@"C:tempb.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 中的僵尸,我想到了这一点,并想到别人会发现它有趣 !

在重负载的关键系统上,编写lock-free代码主要是因为性能 improvments 。 查看像 LMAX 之类的内容,以及它如何利用"机械同情"来对这里进行出色的讨论。 尽管担心僵尸线程? 我认为这是一个边缘情况,只是一个 Bug 要被修整,而不是一个足够好的理由不使用 lock

听起来更像你的朋友他是正处于幻想的和 flaunting knowledege晦涩的外来术语给我吧 ! 在我一直在运行微软的性能实验室的时候,我从未遇到过这个问题的实例

...