【问题标题】:Is there a way to prevent the ThreadPool from making garbage?有没有办法防止 ThreadPool 制造垃圾?
【发布时间】:2016-01-16 01:40:53
【问题描述】:

执行多线程方法会产生垃圾。为什么会这样,我们可以预防吗?

ThreadPool.QueueUserWorkItem(callBack, state);

编辑: 我所说的垃圾是指创建然后超出范围的对象。垃圾收集非常慢,因为它是旧版本的单声道。因此,您从 GC 中节省的每一个 kb 都是一次胜利。如果您不熟悉统一引擎,请在屏幕截图中高亮显示的行中查看 GC 列。它说 0.6kb。因此它会产生 600 字节的垃圾。回调代码不会产生任何垃圾,因此它源于 ThreadPool.QueueUserWorkItem

编辑 2:为了进一步阐述,这里有一个更具体的例子:

public class TestThread : MonoBehaviour
{
    public void Update()
    {
        if (Time.frameCount%10 == 0)
            ThreadPool.QueueUserWorkItem(DummyMethod);
    }

    public void DummyMethod(object meaningless)
    {
    }
}

这是结果。请查看突出显示的行。 GC 列显示 285Bytes。由于 DummyMethod 没有做任何事情,所以垃圾与 ThreadPool 有关。

编辑 3: 为了缓解这种情况并找到替代方案,可以接受一个工作线程来执行队列中的作业。

没关系,但是如果有多个 CPU 可用,它必须在除了一个 unity 使用的 CPU 上运行。 Unity 几乎在一个线程中执行任何操作,因此同一 CPU 上的后台工作人员将是一场灾难。此外,它是一个跨平台项目,因此仅限 Windows 的解决方案将不起作用。所以基本上我需要一个工作线程解决方案,并知道是否有可能实现一个线程的 CPU 是否与另一个线程的相同。

【问题讨论】:

  • “制造垃圾”?这是什么意思?
  • 做很多任何事情都会产生垃圾,以后需要清理。但总的来说,多线程意味着开销。这是野兽的本性。
  • 17 kb gc 已分配。有问题吗?
  • 没有任何代码显示您在这些线程中实际执行的操作,我不确定您认为这个问题将如何回答。
  • 如果 17kb 真的是个问题,你可以尝试不使用 ThreadPool,而直接自己创建线程。可能实现自己的无垃圾(TM)线程池?

标签: c# unity3d


【解决方案1】:

当您 ThreadPool.QueueUserWorkItem(DummyMethod); 它实际上是在隐式地将您的代码转入 ThreadPool.QueueUserWorkItem(new WaitCallback(DummyMethod)); 时,该回调可能是被放入 GC 的项目。尝试使用以下代码显式创建委托并保留对它的引用,看看它是否减少了 GCable 数据量。

public class TestThread : MonoBehaviour
{
    private readonly WaitCallback _callback;

    public TestThread()
    {
        _callback = new WaitCallback(DummyMethod);
    }

    public void Update()
    {
        if (Time.frameCount%10 == 0)
            ThreadPool.QueueUserWorkItem(_callback);
    }

    public void DummyMethod(object meaningless)
    {
    }
}

更新:这是一个非常基本的单线程后台工作程序实现,为您提供一个起点。下面的代码未经测试,性能可能很糟糕,但它确实为您提供了一个起点。

public class BasicBackgroundWorker
{
    private readonly Thread _backgroundWorkThread;
    private readonly Queue<Action> _queue = new Queue<Action>();
    private readonly ManualResetEvent _workAvailable = new ManualResetEvent(false);

    public BasicBackgroundWorker()
    {
        _backgroundWorkThread = new Thread(BackgroundThread)
        {
            IsBackground = true,
            Priority = ThreadPriority.BelowNormal,
            Name = "BasicBackgroundWorker Thread"
        };
        _backgroundWorkThread.Start();
    }

    public void EnqueueWork(Action work)
    {
        lock (_queue)
        {
            _queue.Enqueue(work);
            _workAvailable.Set();
        }
    }

    private void BackgroundThread()
    {
        while (true)
        {
            _workAvailable.WaitOne();
            Action workItem;
            lock (_queue)
            {
                workItem = _queue.Dequeue();
                if (_queue.Count == 0)
                {
                    _workAvailable.Reset();
                }
            }
            try
            {
                workItem();
            }
            catch (Exception ex)
            {
                //Log exception that happened in backgroundWork
            }
        }
    }
}

【讨论】:

  • 这帮助了 16 个字节。谢谢。
  • 我认为您在使用QueueUserWorkItem 的同时不会变得更好,任何其他改进都需要编写您自己编写的自定义线程池。
  • Unity 是否允许您执行new Thread(WorkerMethod) { IsBackground = true };?如果是这样,您可以设置一个从 BlockingCollection&lt;T&gt; 工作中提取的专用后台工作人员。
  • 你不应该关心它在什么 CPU 上。只需将线程优先级设置为低即可。 UI 线程在所有 CPU 上运行,它在上下文切换发生时从 CPU 跳转到 CPU,它没有 CPU 亲和性。强制它“不在同一个 CPU 上”也会导致您的代码根本无法在单核机器上运行。
  • 我已经用一个非常基本的后台工作人员更新了我的答案,供您作为起点。
猜你喜欢
  • 2018-11-28
  • 1970-01-01
  • 2019-02-15
  • 2015-10-11
  • 1970-01-01
  • 2012-09-21
  • 2020-10-10
  • 2018-09-11
  • 2019-11-27
相关资源
最近更新 更多