【问题标题】:Threadpool in C# too slow, is there a way to speed up it? Thread.Sleep(0) and QueueUserWorkItem issuesC#中的线程池太慢了,有没有办法加快速度? Thread.Sleep(0) 和 QueueUserWorkItem 问题
【发布时间】:2011-12-10 14:49:08
【问题描述】:

我在需要执行一些 CPU 密集型工作的 C# 应用程序中使用线程池。顺便说一句,它似乎太慢了(编辑:它每 10 秒只打印几次调试字符串"Calculating on " + lSubArea.X + ":" + lSubArea.Y + " " + lSubArea.Width + ":" + lSubArea.Height,而我希望每几秒至少看到 NUM_ROWS_GRID^2 = 16 次),还通过更改 MinThreads SetMinThreads 方法。我不知道是否切换到自定义线程或者是否有办法加快它。在 Google 上搜索会返回一些结果,但没有任何效果;与 MSDN 的情况相同。

旧代码如下:

private void StreamerRoutine()
{
   if (this._state.Area.Width == 0 && this._state.Area.Height == 0)
      this._state.Area = new Rectangle(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

   while (this._state.WorkEnd == false)
   {
      // Ends time slice if video is off
      if (this._state.VideoOn == false)
         Thread.Sleep(0);
      else
      {
         lock(this._state.AreaSync)
         {
             Int32 lWidth = this._state.Area.Width / Constants.NUM_ROWS_GRID;
             Int32 lHeight = this._state.Area.Height / Constants.NUM_ROWS_GRID;
             for (Int32 lX = 0; lX + lWidth <= this._state.Area.Width; lX += lWidth)
                for (Int32 lY = 0; lY + lHeight <= this._state.Area.Height; lY += lHeight)
                   ThreadPool.QueueUserWorkItem(CreateDiffFrame, (Object)new Rectangle(lX, lY, lWidth, lHeight));
         }
      }
    }
}

private void CreateDiffFrame(Object pState)
{
   Rectangle lSubArea = (Rectangle)pState;

   SmartDebug.DWL("Calculating on " 
          + lSubArea.X + ":" + lSubArea.Y + " " 
          + lSubArea.Width + ":" + lSubArea.Height);
   // TODO : calculate frame
   Thread.Sleep(0);
}

编辑:CreateDiffFrame 函数只是我曾经知道每秒调用多少次的存根。它将被 CPU 密集型工作取代,因为我定义了在这种情况下使用线程的最佳方式。

编辑:我删除了所有 Thread.Sleep(0);我认为这可能是一种加快例行程序的方法,但似乎它可能是一个瓶颈.. 新代码如下:

编辑:我将 WorkEnd 和 VideoOn 设置为 volatile 以避免缓存值和无限循环;我还添加了一个信号量,以使每一组工作项在前一组完成后开始。现在它工作得很好

private void StreamerRoutine()
    {
        if (this._state.Area.Width == 0 && this._state.Area.Height == 0)
            this._state.Area = new Rectangle(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);

        this._state.StreamingSem = new Semaphore(Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID, Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID);


        while (this._state.WorkEnd == false)
        {
            if (this._state.VideoOn == true)
            {
                for (int i = 0; i < Constants.NUM_ROWS_GRID * Constants.NUM_ROWS_GRID; i++)
                    this._state.StreamingSem.WaitOne();

                lock(this._state.AreaSync)
                {
                    Int32 lWidth = this._state.Area.Width / Constants.NUM_ROWS_GRID;
                    Int32 lHeight = this._state.Area.Height / Constants.NUM_ROWS_GRID;
                    for (Int32 lX = 0; lX + lWidth <= this._state.Area.Width; lX += lWidth)
                        for (Int32 lY = 0; lY + lHeight <= this._state.Area.Height; lY += lHeight)
                            ThreadPool.QueueUserWorkItem(CreateDiffFrame, (Object)new Rectangle(lX, lY, lWidth, lHeight));

                }
            }
        }
    }

private void CreateDiffFrame(Object pState)
    {
        Rectangle lSubArea = (Rectangle)pState;

        SmartDebug.DWL("Calculating on " + lSubArea.X + ":" + lSubArea.Y + " " + lSubArea.Width + ":" + lSubArea.Height);
        // TODO : calculate frame
        this._state.StreamingSem.Release(1);

    }

【问题讨论】:

  • 为什么大家都在睡觉?
  • 为什么大家都在睡觉?这是阻塞线程池线程而不是允许它们用于不同的作业。这将是非常低效的,并且不是线程池的预期使用方式。当您的示例代码在池中几乎没有要执行的工作时,我看不出您如何声称线程池很慢。
  • 它可以在前一堆完成之前对项目进行排队。然后this._state.WorkEndthis._state.VideoOn 在循环中永远不会改变,这也可能很危险,但可能一些隐式内存屏障可以让你避免成为一个实际的错误。
  • 设置布尔值是原子的,但相关的内存屏障太弱了。但是原子性是不够的,读取代码可能会在一个陈旧的缓存值上工作,除非你之间有内存屏障。但你很幸运,你的锁和睡眠提供了这些障碍。如果不添加至少一个注释来记录这些障碍在哪里,以及哪些代码依赖于它们,我就不会依赖这些隐式内存障碍。 |如果新批次到货时您唯一的项目仍在工作,它们可能会在新批次上工作,因为您没有传递对不可变图像数据的引用。
  • 如果WorkEndVideoOn 不是易失性,您编辑的代码可能会死锁。由于一旦它们都为假,计算机可以决定它们都将永远保持false 并进入无限循环。

标签: c# multithreading performance threadpool queueuserworkitem


【解决方案1】:

从我所看到的情况来看,确实没有一个好的方法可以准确地告诉您是什么让您的代码变慢,但是有几件事很突出:

  1. Thread.Sleep(0)。当你这样做时,你会放弃操作系统的剩余时间片,并减慢一切,因为 CreateDiffFrame() 在操作系统调度程序返回之前实际上无法返回。

  2. Rectangle 的对象转换,它是一个结构。发生这种情况时,您会产生 boxing 的开销,这对于真正的计算密集型操作来说不是您想要的。

  3. 您对 lock(this._state.AreaSync) 的调用。可能是 AreaSync 也被锁定在其他地方,这可能会减慢速度。

  4. 您对项目的排队可能过于精细 - 如果您对非常小的工作项目进行排队,与实际数量相比,一次将这些项目放入队列的开销可能会很大完成工作。您也许还可以考虑将内部循环的内容放在排队的工作项中以减少这种开销。

如果这是您尝试为并行计算做的事情,您是否使用PLINQ 或其他此类框架进行了调查?

【讨论】:

  • 好的,谢谢,1 也许这就是问题 2 我会尝试看看我是否可以使用不同的结构,3 我确定它没有锁定在其他地方,4 可能,我在想也许我必须使用粒度更大的线程池。关于PLINQ我不知道,现在我去看看谢谢:)
【解决方案2】:

我的猜测是 CreateDiffFrame 末尾的睡眠。如果我没记错的话,这意味着每个线程至少还能再存活 10 毫秒。您可能可以在不到 10 毫秒的时间内完成实际工作。 ThreadPool 试图优化线程的使用,但我认为它对未完成线程的总数有一个上限。因此,如果您想实际模拟您的工作负载,请创建一个紧密循环,等待经过预期的毫秒数,而不是休眠。

无论如何,我不认为使用 ThreadPool 是真正的瓶颈,使用其他线程机制不会加速你的代码。

【讨论】:

  • 不,Sleep(0) 只会让出 CPU。这是一个坏的/不必要的想法。
【解决方案3】:

KB976898 中描述的ThreadPool.SetMinThreads 方法存在已知错误:

使用 Microsoft .NET Framework 3.5 中的 ThreadPool.SetMinThreads 方法后,线程池维护的线程无法按预期工作

您可以从 here 下载此行为的修复程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-18
    • 1970-01-01
    • 2020-04-01
    • 2013-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多