ThreadPool.QueueUserWorkItem vs Parallel.For
我想了解Parralel.For
和ThreadPool.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个工作线程可用)。
http://msdn.microsoft.com/en-us/library/system.threading.threadpool.setminthreads%28v=vs.110%29.aspx
在这种情况下,线程池最小工作线程默认值是4 - 这也解释了为什么只有4个线程使用。 该文档声明ThreadPool可以决定是否在达到最小值时创建更多的线程。
将最小工作线程数设置为5时,将创建5个线程,并且所有任务都按预期同时完成。
我相信“活动”线程和“可用”线程之间存在差异。线程池将重新使用线程,如果它决定需要更多线程,它将开始将可用线程移动到活动线程。如果你想一次开始许多事情,这可能会令人沮丧,因为每个可用的线程可能需要大约2秒才能启动。
作为其线程管理战略的一部分,在创建线程之前线程池延迟。因此,当很多任务在短时间内排队时,在所有任务开始之前可能会有显着的延迟。
如果您要反复运行这些任务或添加任务,您应该会看到其他线程变为活动状态。
所有并行任务都在多个线程中完成,这意味着线程是并行任务的基本单元。 所以,我认为线程池比TPL更有效率。 为什么? 因为TPL的默认任务调度程序是ThreadPoolTaskScheduler:
private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler();
,让我们看看ThreadPoolTaskScheduler:
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
。
道歉 - 输出2的结果中存在拼写错误。我更新了它 - 真正使用了第五个线程。 – Fortmann
毫无疑问,线程可以在几毫秒内启动(在ThreadPool的情况下,现有线程可以更快地分派)。为什么线程池决定不派遣第五个线程后,我已经明确要求它为第五个任务做到这一点?前四个和最后一个线程之间的延迟恰好是一秒,这表明延迟不是由线程管理员造成的,管理员正在等待前四个中的一个完成。 – Fortmann
创建线程可能是一项昂贵的任务。如果您要求它运行1000个任务会怎么样?它不会启动1000个线程,但它会缓慢增加,直到达到平衡。我怀疑如果你将睡眠时间增加到5秒作为测试,你会看到线程池中的算法决定启动一个新的线程。我个人使用一个线程池来处理负载变化很大的服务。游泳池将有40个线程活动到200个任意位置。在短期内,任务可能需要更长时间,但平均而言,游泳池在找到最佳性能点方面做得很好。 – AFrieze