2013-12-19 60 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