僵尸存在......在.NET中?

本文翻译自:Do zombies exist … in .NET?

I was having a discussion with a teammate about locking in .NET. 我和队友讨论过锁定.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. 无论如何,他认为,如果可能的话,应该避免在预期会处于重负载的关键系统上进行.NET锁定,以避免“僵尸线程”崩溃系统的可能性很小。 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. 我并不完全相信这是一个可能在.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: 我也有能力为自己评估这个,因为我知道lock(obj)语句实际上只是语法糖:

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 . 并且因为Monitor.EnterMonitor.Exit被标记为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. 似乎可以想象.NET会做某种处理来保护线程免受可能产生这种影响的系统组件的影响,但这纯粹是推测性的,可能只是基于我从未听说过“僵尸线程”的事实。之前。 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? 僵尸线程可以在.NET上出现吗? (Why/Why not?) (为什么/为什么不?)
  3. If applicable, How could I force the creation of a zombie thread in .NET? 如果适用,我如何强制在.NET中创建僵尸线程?
  4. If applicable, How can I leverage locking without risking a zombie thread scenario in .NET? 如果适用,如何利用锁定而不会在.NET中冒着僵尸线程的风险?

Update 更新

I asked this question a little over two years ago. 两年多前我问过这个问题。 Today this happened: 今天发生这种事:

对象处于僵尸状态。


#1楼

参考:https://stackoom.com/question/1MC1c/僵尸存在-在-NET中


#2楼

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. 仙人蓝指出,在.NET 2.0及finally块免疫线程中止。 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: 正如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");
    }
}

However when using a lock() { } block, the finally would still be executed when a ThreadAbortException is fired that way. 但是当使用lock() { }块时,如果以这种方式触发ThreadAbortException则仍然会执行finally

The following information, as it turns out, is only valid for .NET 1 and .NET 1.1: 事实证明,以下信息仅适用于.NET 1和.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. 如果在lock() { }块内部发生另一个异常,并且ThreadAbortException准确到达finally块即将运行时,则不会释放锁。 As you mentioned, the lock() { } block is compiled as: 如前所述, lock() { }块编译为:

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

If another thread calls Thread.Abort() inside the generated finally block, the lock may not be released. 如果另一个线程在生成的finally块内调用Thread.Abort() ,则可能不会释放锁。


#3楼

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. 看看像LMAX这样的东西,以及它如何利用“机械同情”来讨论这个问题。 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 . 我认为这是一个边缘案例,只是一个需要解决的错误,而不是一个不使用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. 在我运行Microsoft UK的​​性能实验室的过程中,我从未在.NET中遇到过这个问题的实例。


#4楼

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). 因此,给定该定义,然后是,您可以在.NET中执行此操作,就像使用其他语言(C / C ++,java)一样。

However , I do not think this as a good reason not to write threaded, mission critical code in .NET. 但是 ,我不认为这是不在.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. 可能有其他原因决定反对.NET,但仅仅因为你可以让zombie线程对某些方面没有意义而取消.NET。 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). 僵尸线程在C / C ++中是可能的(我甚至认为它更容易陷入C语言)并且许多关键的线程应用程序都是C / C ++(大批量交易,数据库等)。

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. 结论如果您正在决定使用哪种语言,那么我建议您考虑大局:性能,团队技能,日程安排,与现有应用程序的集成等。当然,僵尸线程是您应该考虑的事情但是,由于与C等其他语言相比,实际上在.NET中犯这个错误实在太难了,我认为这种担忧会被上面提到的其他事情所掩盖。 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. 其他语言(如C / C ++和Java)也是如此。 But this is not a reason not to write threaded code in .NET. 但这不是不在.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. 无论您使用何种语言,任务关键型系统的可靠代码都不容易编写。但我很肯定在.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). 此外,AFAIK,.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. 这显然是不可取的,但由于这种可能性而单独使用.NET可能不是一个好主意,因为它也可能在其他语言中使用。 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? 我甚至认为在C / C ++中比在.NET中更容易陷入困境(特别是在你没有RAII的C中),但很多关键应用程序都是用C / C ++编写的吗? 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. 如果你想从你的应用程序中提取每一盎司的速度并希望尽可能接近裸机,那么.NET 可能不是最好的解决方案。 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. 如果您的预算紧张并且与Web服务/现有.net库/等进行大量接口,则.NET 可能是一个不错的选择。


#5楼

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. 这不是关于Zombie线程,但是有效C#这本书有一个关于实现IDisposable的部分(第17项),它讨论了我认为你可能会感兴趣的Zombie对象。

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. 我建议阅读本书,但其中的要点是,如果你有一个类实现IDisposable,或者包含一个Desctructor,那么你应该做的唯一事情就是释放资源。 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. 到达foreach循环时,Undead列表中有150个对象,每个对象都包含一个图像,但是图像已经过GC并且如果您尝试使用它会得到异常。 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: 在这个例子中,当我尝试对图像做任何事情时,我得到一个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:\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! 再一次,我知道你特别询问僵尸线程,但问题标题是关于.net中的僵尸,我被提醒了这一点,并认为其他人可能会发现它很有趣!


#6楼

  • 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 (eg handles) are still around and (potentially) causing problems. 对我来说似乎是一个很好的解释 - 一个已经终止的线程(因此不再释放任何资源),但是其资源(例如句柄)仍然存在且(可能)导致问题。

  • Can zombie threads occur on .NET? 僵尸线程可以在.NET上出现吗? (Why/Why not?) (为什么/为什么不?)
  • If applicable, How could I force the creation of a zombie thread in .NET? 如果适用,我如何强制在.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 . 该程序启动一个线程Target ,它打开一个文件然后使用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). 生成的僵尸线程永远不会释放“test.txt”文件的句柄,因此文件将保持打开状态,直到程序终止(您可以使用进程资源管理器或类似工具进行检查)。 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) 在调用GC.Collect之前,“test.txt”的句柄不会被释放 - 事实证明它比创建泄漏句柄的僵尸线程更困难

  • If applicable, How can I leverage locking without risking a zombie thread scenario in .NET? 如果适用,如何利用锁定而不会在.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 (eg something goes wrong in the CLR). 只要您的代码正确地清理(使用安全句柄或等效类,如果使用非托管资源),并且只要您不以任何方式以奇怪和奇妙的方式杀死线程(最安全的方法是永远不要杀线程-让他们自行终止正常,或通过异常如果需要的话),你将会有类似的东西僵尸线程的唯一途径是,如果事情已经非常错误的(如一些在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). 事实上,创建一个僵尸线程实际上非常困难(我不得不将P / Invoke转换成一个函数,在文档中告诉你不要在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) 尽管犯了一些非常糟糕的错误,但是一旦调用Abort ,“test.txt”的句柄仍然关闭(作为file的终结器的一部分,它使用SafeFileHandle来包装其文件句柄)

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. C.Evenhuis回答中的锁定示例可能是当一个线程以非奇怪的方式终止时无法释放资源(在这种情况下是锁)的最简单方法,但是很容易通过使用lock语句来修复,或者将版本放入finally块中。

See also 也可以看看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
植物大战僵尸png.zip是一种压缩文件格式,其包含了植物大战僵尸游戏的图片文件。PNG是一种无损的图片格式,具有较高的图像质量,适合用于存储游戏的角色、场景、道具等元素的图片。而.zip是一种常见的压缩文件格式,可以将多个文件或文件夹打包成一个文件,以便于传输和存储。 植物大战僵尸是一款策略塔防游戏,玩家需要通过种植各种能量植物来抵挡僵尸的进攻。游戏的各种植物和僵尸都有各自的特点和能力,玩家需要根据情况合理安排植物的种植位置和使用时间,以击败所有的僵尸并保护自己的花园。这款游戏在全球范围内都获得了巨大的成功和认可,成为了经典的塔防游戏之一。 植物大战僵尸png.zip文件可能是包含了游戏的各种png格式的图片素材,可以用于制作游戏攻略、壁纸、表情包等。你可以解压这个文件,提取其的图片素材,并根据需要进行使用。解压缩可以使用解压软件,如WinRAR、7-Zip等。解压后你可以浏览文件夹,并将里面的png格式文件复制到你需要的目录。 这些图片素材可以用于制作自定义的游戏内容,如自定义地图、自定义角色等。也可以在社交媒体平台上分享给其他玩家,展示你的游戏成果和创造力。总之,植物大战僵尸png.zip文件为喜爱这款游戏的玩家提供了一个方便的资源库,可以用于丰富游戏体验和创作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值