.NET GC 从终结器访问同步对象

发布于 2024-10-20 17:57:48 字数 2563 浏览 0 评论 0原文

我最近阅读了这篇文章安全线程同步,因为我对该线程感到好奇从终结器发出的调用的安全性。我编写了以下代码来测试从终结器对静态线程安全集合的访问。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GCThreadTest
{
    class Program
    {
        static class FinaliserCollection
        {
            private static Queue<int> s_ItemQueue = new Queue<int>();
            private static System.Object s_Lock = new System.Object();

            public static void AddItem(int itemValue)
            {
                lock(s_Lock)
                {
                    s_ItemQueue.Enqueue(itemValue);
                }
            }

            public static bool TryGetItem(out int item)
            {
                lock(s_Lock)
                {
                    if (s_ItemQueue.Count <= 0)
                    {
                        item = -1;
                        return false;
                    }

                    item = s_ItemQueue.Dequeue();
                    return true;
                }
            }
        }

        class FinaliserObject
        {
            private int m_ItemValue;

            public FinaliserObject(int itemValue)
            {
                m_ItemValue = itemValue;
            }

            ~FinaliserObject()
            {
                FinaliserCollection.AddItem(m_ItemValue);
            }
        }

        static void Main(string[] args)
        {
            int itemValueIn = 0;
            int itemValueOut = 0;

            while (itemValueOut < 10000)
            {
                System.Threading.ThreadPool.QueueUserWorkItem
                    (delegate(object value)
                    {
                        new FinaliserObject((int)value);

                        System.Threading.Thread.Sleep(5);

                    }, itemValueIn);

                itemValueIn = itemValueIn + 1;

                // This seems to stop finaliser from
                // being called?
                // System.Threading.Thread.Sleep(5);

                int tempItemValueOut = -1;
                if (FinaliserCollection.TryGetItem(out tempItemValueOut))
                    itemValueOut = tempItemValueOut;
            }

            System.Console.WriteLine("Finished after {0} items created", itemValueOut);
            System.Console.ReadLine();
        }
    }
}

如果没有 while 循环中的“Sleep”调用,这段代码似乎运行良好,但它真的不会死锁吗?当排队的线程池项访问静态集合时,是否可以进行终结器调用?为什么将“睡眠”添加到主线程 while 循环似乎会阻止所有终结器被调用?

I recently read this article Safe Thread Synchronization as I was curious about the thread safety of calls made from a finaliser. I wrote the following code to test access to a static thread safe collection from a finaliser.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GCThreadTest
{
    class Program
    {
        static class FinaliserCollection
        {
            private static Queue<int> s_ItemQueue = new Queue<int>();
            private static System.Object s_Lock = new System.Object();

            public static void AddItem(int itemValue)
            {
                lock(s_Lock)
                {
                    s_ItemQueue.Enqueue(itemValue);
                }
            }

            public static bool TryGetItem(out int item)
            {
                lock(s_Lock)
                {
                    if (s_ItemQueue.Count <= 0)
                    {
                        item = -1;
                        return false;
                    }

                    item = s_ItemQueue.Dequeue();
                    return true;
                }
            }
        }

        class FinaliserObject
        {
            private int m_ItemValue;

            public FinaliserObject(int itemValue)
            {
                m_ItemValue = itemValue;
            }

            ~FinaliserObject()
            {
                FinaliserCollection.AddItem(m_ItemValue);
            }
        }

        static void Main(string[] args)
        {
            int itemValueIn = 0;
            int itemValueOut = 0;

            while (itemValueOut < 10000)
            {
                System.Threading.ThreadPool.QueueUserWorkItem
                    (delegate(object value)
                    {
                        new FinaliserObject((int)value);

                        System.Threading.Thread.Sleep(5);

                    }, itemValueIn);

                itemValueIn = itemValueIn + 1;

                // This seems to stop finaliser from
                // being called?
                // System.Threading.Thread.Sleep(5);

                int tempItemValueOut = -1;
                if (FinaliserCollection.TryGetItem(out tempItemValueOut))
                    itemValueOut = tempItemValueOut;
            }

            System.Console.WriteLine("Finished after {0} items created", itemValueOut);
            System.Console.ReadLine();
        }
    }
}

Without the 'Sleep' call in the while loop this code seems to run fine but is it really safe from deadlocking? Would it ever be possible for a finaliser call to be made while a queued thread pool item is accessing the static collection? Why does adding the 'Sleep' to the main threads while loop appear to stop all finalisers from being called?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

因为看清所以看轻 2024-10-27 17:57:48

哇。这是什么……这是我见过的最奇怪的代码。 @.@

首先,你指的终结器调用是什么?我看到的唯一终结器是 FinaliserObject 的终结器,它将被调用 10,000 次,并且可以独立于静态集合上发生的情况进行调用。 IE 是的,当其他对象从集合中出队时,这些对象可以被销毁。这不是问题。

在应用程序本身退出之前,静态集合本身不会被清理。

请记住,绝对无法保证在应用程序本身退出之前何时或是否会调用这些终结器。当您退出时,您的静态集合可能完全为空。

更糟糕的是,您将 itemValueOut 分配给从队列中提取的最后一个值......这不是创建的项目数,正如您在 WriteLine() 中所暗示的那样。因为这些析构函数以任何可能的顺序调用,所以理论上您可以按该顺序将 10,000、9,999、9,998、... 2、1 添加到队列中。

这又是一个问题,因为您从队列中删除了 10,000 次,但在最后一个循环中,很可能没有对象要出队,在这种情况下,您保证会得到 -1 的数字退回的商品数量(即使其他 9,999 件商品成功运行)。

为了回答你的问题,这段代码不会死锁。如果 AddItem() 调用 TryGetItem(),就会发生死锁,但在添加或删除项目时,这些锁几乎可以保证将彼此排除在静态集合之外。

您尝试命运的地方是,您可以退出应用程序,而无需所有 FinaliserObject 将自身添加到队列中。这意味着终结器之一可能会触发并尝试添加到 FinaliserCollection,但 FinaliserCollection 已被释放。你在决赛中所做的事情糟糕

但是,当您调用 FinaliserCollection.TryGetItem() 时,可能会发生终结器调用。终结器将阻塞并等待,直到 TryGetItem()lock() 中出现,此时它将添加另一个项目。这不是问题。

至于 sleep() 命令,您可能只是忽略了垃圾收集的时间。请记住,在 GC 决定需要资源之前,您的对象不会被收集/最终确定。

抱歉这么强调......我知道你只是想测试一个概念,但我真的不明白为什么你会想要在终结器中做你想做的事情。如果这里确实有一个合法的目标,那么在终结器中执行它并不是正确的答案。

编辑

从我读到的和萨沙所说的来看,不,你不会陷入僵局。终结器线程可能会因等待锁而被阻塞,但 GC 不会等待终结器,因此会取消挂起线程,从而释放锁。

无论如何,这是一个非常有力的论据,说明为什么您不应该在终结器中进行这样的调用...终结器用于释放非托管资源。其他的都是在玩轮盘赌。

Wow. What the... This is the most bizarre piece of code I've ever seen. @.@

First of all, what finalizer call are you referring to? The only finalizer I see is the finalizer for the FinaliserObject, which will be called 10,000 times, and can be called independently of whatever's going on on the static collection. I.E. yes, those objects can be destroyed while other objects are being dequeued from the collection. This isn't an issue.

The static collection itself won't be cleaned up until the app itself exits.

Keep in mind that there's absolutely no guarantee when or if those finalizers will be called before the app itself exits. Your static collection could be completely empty when you exit.

Worse, you're assigning itemValueOut to whatever the last value you pull out of the queue is... which is NOT the number of items created, as you imply in your WriteLine(). Because those destructors are called in any possible order, you could theoretically add to the queue 10,000, 9,999, 9,998, ... 2, 1, in that order.

Which is further an issue, because you're removing from the queue 10,000 times, but on the last loop, it's very possible there won't be an object to dequeue, in which case you're guaranteed to get -1 for the number of items returned (even if the other 9,999 items worked successfully).

To answer your question, this code cannot deadlock. A deadlock would happen if AddItem() called TryGetItem(), but those locks are pretty much guaranteed to keep each other out of the static collection while adding or removing items.

Where you're tempting fate is that you can exit your app without all of the FinaliserObjects having added themselves to the queue. Meaning one of the finalizers could fire and try to add to the FinaliserCollection, but the FinaliserCollection has already been disposed. What you're doing in the finaliser is terrible.

But yes, a finalizer call can happen while you're calling FinaliserCollection.TryGetItem(). The finalizer will block and wait until TryGetItem() emerges from the lock(), at which point it will add another item. This is not an issue.

As for the sleep() command, you're probably just throwing the timing of the garbage collection off. Remember, your objects won't be collected/finalized until the GC decides it needs the resources.

Sorry for being so emphatic... I know you're just trying to test a concept but I really don't understand why you would want to do what you're trying to do in the finalizer. If there's really a legitimate goal here, doing it in the finalizer is not the correct answer.

Edit

From what I'm reading and what Sasha is saying, no you will not have a deadlock. The finalizer thread may be blocked waiting for the lock, but the GC will not wait for the finalizer, and will thus unsuspend the threads, allowing the locks to be released.

In any case, this is a very strong argument for why you shouldn't be making calls like this in a finalizer... the finalizer is only for releasing unmanaged resources. Anything else is playing roulette.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文