【问题标题】:ThreadPool.QueueUserWorkItem vs Parallel.ForThreadPool.QueueUserWorkItem 与 Parallel.For
【发布时间】:2014-01-08 07:05:11
【问题描述】:

我试图了解Parralel.ForThreadPool.QueueUserWorkItem 之间的区别。

硬件和软件:

  • 英特尔 i5(四核)
  • Windows 7 64 位专业版
  • 点网 4.5

案例 1 代码:线程池

for (int index = 0; index < 5; index++)
{
  ThreadPool.QueueUserWorkItem((indexParam) =>
  {
    int threadID = Thread.CurrentThread.ManagedThreadId;
    Thread.Sleep(1000);
    BeginInvoke((Action)delegate { listBox1.Items.Add("Completed " + indexParam.ToString() + " using thread " + threadID.ToString() + "  (" + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString("000") + ")"); });
  }, index);
}

输出:

使用线程 10 (45.871) 完成 0
使用线程 11 (45.875) 完成 1
使用线程 12 (45.875) 完成 2
使用线程 13 (45.875) 完成 3
使用线程 10 (46.869) 完成 4

案例 2 代码:Parallel.For

  ParallelLoopResult result = Parallel.For(0, 5, (int index, ParallelLoopState loopState) =>
  {
    int threadID = Thread.CurrentThread.ManagedThreadId;
    Thread.Sleep(1000);
    BeginInvoke((Action)delegate { listBox1.Items.Add("Completed " + index.ToString() + " using thread " + threadID.ToString() + "  (" + DateTime.Now.Second.ToString() + "." + DateTime.Now.Millisecond.ToString("000") + ")"); });
  });

输出:

使用线程 10 (16.923) 完成 0
使用线程 11 (16.925) 完成 1
使用线程 12 (16.925) 完成 2
使用线程 13 (16.926) 完成 3
使用线程 14 (16.926) 完成 4

问题:

从案例 1 的结果来看,似乎只有四个线程处于活动状态,然后使用第一个空闲线程完成最终任务。在情况 2 中,似乎有五个线程立即专用并“同时”执行。

为什么 QueueUserWorkItem 的线程处理不像并行类那样使用第五个线程?

(ThreadPool.GetAvailableThreads(...) 确认有 1023 个工作线程可用)。

【问题讨论】:

    标签: .net multithreading parallel.foreach queueuserworkitem


    【解决方案1】:

    所有并行任务都是在多个线程中完成的,也就是说,线程是并行任务的基本单元。 所以,我认为线程池比 TPL 更高效。 为什么? 因为TPL的默认任务调度器是ThreadPoolTask​​Scheduler:

    私有静态只读TaskScheduler s_defaultTaskScheduler = new ThreadPoolTask​​Scheduler();

    让我们看看 ThreadPoolTask​​Scheduler:

        protected internal override void QueueTask(Task task)
        {
            if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
            {
                new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
                {
                    IsBackground = true
                }.Start(task);
                return;
            }
            bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) != TaskCreationOptions.None;
            ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
        }
    

    那么,我们来看看threadpool

    internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
    {
        ThreadPool.EnsureVMInitialized();
        try
        {
        }
        finally
        {
            ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
        }
    }
    

    好的...让我们看看我们的另一个选择:

    public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object state)
    {
        StackCrawlMark stackCrawlMark = StackCrawlMark.LookForMyCaller;
        return ThreadPool.QueueUserWorkItemHelper(callBack, state, ref stackCrawlMark, false);
    }
    

    好的..让我们继续挖掘:

    private static bool QueueUserWorkItemHelper(WaitCallback callBack, object state, ref StackCrawlMark stackMark, bool compressStack)
    {
        bool result = true;
        if (callBack != null)
        {
            ThreadPool.EnsureVMInitialized();
            try
            {
                return result;
            }
            finally
            {
                QueueUserWorkItemCallback callback = new QueueUserWorkItemCallback(callBack, state, compressStack, ref stackMark);
                ThreadPoolGlobals.workQueue.Enqueue(callback, true);
                result = true;
            }
        }
        throw new ArgumentNullException("WaitCallback");
    }
    

    现在,我们终于找到了相同的点。 所以,哪个更好,这是你的选择。

    这就是为什么我从不使用TPL,而是直接使用threadpool

    【讨论】:

      【解决方案2】:

      http://msdn.microsoft.com/en-us/library/system.threading.threadpool.setminthreads%28v=vs.110%29.aspx

      在这种情况下,ThreadPool 最小工作线程默认值为 4 - 这解释了为什么只使用 4 个线程。 文档指出,一旦达到最小值,ThreadPool 可能会决定是否创建更多线程。

      将最小工作线程数设置为 5 时,将创建 5 个线程,所有任务按预期同时完成。

      【讨论】:

        【解决方案3】:

        我相信“活动”线程和“可用”线程之间存在差异。线程池将重用线程,如果它决定需要更多线程,它将开始将可用线程移动到活动线程。如果您想一次启动许多事情,这可能会令人沮丧,因为每个可用线程可能需要大约 2 秒才能启动。

        MSDN Managed Threadpool

        作为其线程管理策略的一部分,线程池在创建线程之前会延迟。因此,当多个任务在短时间内排队时,在所有任务启动之前可能会有很大的延迟。

        如果您要重复运行这些任务或添加任务,您应该会看到其他线程处于活动状态。

        【讨论】:

        • 抱歉 - 输出 2 的结果中有错字。我已经更新了 - 确实使用了第五个线程。
        • 当然线程可以在几毫秒内启动(在 ThreadPool 的情况下,现有线程可以更快地调度)。为什么 ThreadPool 在我特别要求它为第五个任务这样做之后决定不分派第五个线程?前四个和最后一个线程之间的延迟正好是一秒,这表明延迟不是由Thread admin引起的,管理层具体是在等待前四个中的一个完成。
        • 创建线程可能是一项昂贵的任务。如果您要求它运行 1000 个任务怎么办?它不会启动 1000 个线程,但会缓慢增加直到达到平衡。我怀疑如果您将睡眠时间增加到 5 秒作为测试,您将看到线程池中的算法决定启动一个新线程。我个人将线程池用于负载变化很大的服务。池中的活动线程从 40 到 200 不等。短期内..一个任务可能需要更长的时间,但平均而言,池在找到最佳性能点方面做得很好。
        • 注意:管理员正在等待一个线程完成,如果你说,5秒,我怀疑管理员会决定是否值得启动一个新线程。
        • 使用 ThreadPool 启动 40 个任务时,行为仍然相同,四个任务总是同时运行(仅在四个线程上),然后,只有在一个任务完成后,ThreadPool 才会将队列中的下一个任务分配给那个线程。为什么 ThreadPool 不会启动超过四个线程?
        猜你喜欢
        • 2011-09-05
        • 2011-03-04
        • 2014-07-29
        • 1970-01-01
        • 2012-03-01
        • 2014-10-18
        • 1970-01-01
        • 2012-10-15
        • 1970-01-01
        相关资源
        最近更新 更多