【问题标题】:.NET GC Accessing a synchronised object from a finalizer.NET GC 从终结器访问同步对象
【发布时间】:2011-03-07 03:15:24
【问题描述】:

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

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 循环中的“睡眠”调用,这段代码似乎运行良好,但它真的可以避免死锁吗?当排队的线程池项目正在访问静态集合时,是否有可能进行终结器调用?为什么将“睡眠”添加到主线程 while 循环似乎会阻止所有终结器被调用?

【问题讨论】:

    标签: c# .net multithreading garbage-collection


    【解决方案1】:

    哇。什么... 这是我见过的最离奇的一段代码。 @.@

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

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

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

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

    这是一个更进一步的问题,因为您要从队列中删除 10,000 次,但在最后一个循环中,很可能不会有对象要出队,在这种情况下,您肯定会得到 -1返回的项目数(即使其他 9,999 个项目成功运行)。

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

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

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

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

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


    编辑

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

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

    【讨论】:

    • 为此+1:我知道你只是想测试一个概念,但我真的不明白你为什么要在终结器中做你想做的事情。如果这里真的有一个合法的目标,那么在终结器中执行它并不是正确的答案。
    • 这是对 .NET 对象持有指向引用计数 C++ 对象的指针的问题调查的一部分,这可能导致终结器破坏 C++ 对象。问题是这可能来自随机线程,并且执行的操作可能难以使线程安全。然而,我并不是说这是解决方案,我只是对这个案例的细节感到好奇。
    • 回复中的倒数第三段直指问题的核心。这是我需要澄清的地方。根据我对文章和 MSDN 文档的理解,GC 在执行垃圾收集时会挂起其他工作线程(当它们达到安全点时)。这不包括终结器的执行吗?
    • @karmasponge:不,因为终结器不会在 GC 期间运行。一个特殊的终结器线程运行终结器,并在相关的 GC 周期完成后运行它们。 (此外,GC 的某些部分可以与应用程序的线程同时运行;请参阅“工作站 GC”。)
    • @Sasha:澄清一下,GC 在垃圾收集该对象之前调用对象的终结器(正如您所指出的,终结器在特殊线程上执行)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-24
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 2011-02-12
    相关资源
    最近更新 更多