2013-12-19 66 views
1

我想了解Parralel.ForThreadPool.QueueUserWorkItem之间的差异。ThreadPool.QueueUserWorkItem vs Parallel.For

硬件&软件:

  • 英特尔酷睿i5(四核)
  • Windows 7的64位教授
  • DOTNET的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)
使用螺纹11(45.875)已完成1
使用螺纹12(45.875)完成2
使用螺纹13已完成3(45.875)

完成0
使用螺纹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)
使用螺纹11(16.925)已完成1
使用螺纹12(16.925)完成2
使用螺纹13已完成3

完成0 (16.926)
已完成4使用线程14(16.926)

问题:

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

为什么QueueUserWorkItem的线程处理不使用第五个线程,比如并行类?

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

回答

0

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

MSDN Managed Threadpool

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

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

+0

道歉 - 输出2的结果中存在拼写错误。我更新了它 - 真正使用了第五个线程。 – Fortmann

+0

毫无疑问,线程可以在几毫秒内启动(在ThreadPool的情况下,现有线程可以更快地分派)。为什么线程池决定不派遣第五个线程后,我已经明确要求它为第五个任务做到这一点?前四个和最后一个线程之间的延迟恰好是一秒,这表明延迟不是由线程管理员造成的,管理员正在等待前四个中的一个完成。 – Fortmann

+0

创建线程可能是一项昂贵的任务。如果您要求它运行1000个任务会怎么样?它不会启动1000个线程,但它会缓慢增加,直到达到平衡。我怀疑如果你将睡眠时间增加到5秒作为测试,你会看到线程池中的算法决定启动一个新的线程。我个人使用一个线程池来处理负载变化很大的服务。游泳池将有40个线程活动到200个任意位置。在短期内,任务可能需要更长时间,但平均而言,游泳池在找到最佳性能点方面做得很好。 – AFrieze

0

所有并行任务都在多个线程中完成,这意味着线程是并行任务的基本单元。 所以,我认为线程池比TPL更有效率。 为什么? 因为TPL的默认任务调度程序是ThreadPoolTask​​Scheduler:

private static readonly 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); 
    } 
} 

OK ......让我们来看看我们的其他选择:

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

OK..let的dig more:

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