到底該用多少線程?線程數、CPU核心數、本地計算時間、等待時間的關係 線程數 = CPU核心數 * ( 本地計算時間 + 等待時間 ) / 本地計算時間 下麵是Task.Factory.StartNew和自己寫的TaskHelper.LargeTask.Run對比測試 一、Task.Factory. ...
到底該用多少線程?線程數、CPU核心數、本地計算時間、等待時間的關係 線程數 = CPU核心數 * ( 本地計算時間 + 等待時間 ) / 本地計算時間
下麵是Task.Factory.StartNew和自己寫的TaskHelper.LargeTask.Run對比測試
一、Task.Factory.StartNew 使用 TaskCreationOptions.LongRunning 參數
代碼:
private int n = 50000; //問題規模 private int t = 25; //等待時間 private int pageSize = 1000; //列印分頁 private void TestTaskStartNew() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = Task.Factory.StartNew((obj) => { Thread.Sleep(t); //等待時間 int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i, TaskCreationOptions.LongRunning); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n【Task.Factory.StartNew 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds)); }); }); } private void TestTaskHelper() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = TaskHelper.LargeTask.Run((obj) => { Thread.Sleep(t); //等待時間 int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n【TaskHelper.LargeTask.Run {3}線程 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }View Code
測試結果:
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
【TaskHelper.LargeTask.Run 128線程 問題規模:50000 等待時間:25 耗時:10.5975181秒】
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
【Task.Factory.StartNew 問題規模:50000 等待時間:25 耗時:8.2380754秒】
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
【TaskHelper.LargeTask.Run 128線程 問題規模:50000 等待時間:25 耗時:10.4376939秒】
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
【Task.Factory.StartNew 問題規模:50000 等待時間:25 耗時:9.2322552秒】
測試結果說明:
我的電腦的CPU是i5-8265U,4核8線程
根據等待時間設置合適的線程數對TaskHelper.LargeTask.Run有利
使用TaskHelper.LargeTask.Run運行時的CPU占用在5%以下,創建128個線程的瞬間CPU占用達到30%,使用Task.Factory.StartNew運行時的CPU占用接近100%
資源釋放情況:Task.Factory.StartNew使用TaskCreationOptions.LongRunning參數運行完成後線程數立即釋放,句柄數未立即釋放,而TaskHelper.LargeTask.Run提供了手動釋放的方法可以立即釋放線程數和句柄數,但需要手動調用才能釋放
二、Task.Factory.StartNew 不使用 TaskCreationOptions.LongRunning 參數
代碼:
private int n = 2000; //問題規模 private int t = 100; //等待時間 private int pageSize = 100; //列印分頁 private void TestTaskStartNew() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = Task.Factory.StartNew((obj) => { Thread.Sleep(t); //等待時間 int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n【Task.Factory.StartNew 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds)); }); }); } private void TestTaskHelper() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = TaskHelper.LargeTask.Run((obj) => { Thread.Sleep(t); //等待時間 int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n【TaskHelper.LargeTask.Run {3}線程 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }View Code
測試結果:
0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000
【TaskHelper.LargeTask.Run 96線程 問題規模:2000 等待時間:100 耗時:2.1529565秒】
0 2000 100 200 300 400 500 600 700 800 900 1900 1000 1100 1200 1300 1400 1500 1600 1700 1800
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:17.309869秒】
0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000
【TaskHelper.LargeTask.Run 96線程 問題規模:2000 等待時間:100 耗時:2.143763秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:8.8674353秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:6.5490833秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:5.1381533秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:4.434294秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:4.329009秒】
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:3.6231239秒】
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:3.6303149秒】
測試結論:
Task.Factory.StartNew在不使用TaskCreationOptions.LongRunning參數時,運行大量耗時任務,線程數增加緩慢,導致需要花費很長時間,如果線程池耗盡,或者線程池未耗盡但有大量耗時任務時,其它任務調用Task.Factory.StartNew會有延遲
我想了一天,多任務還是不要共用線程池比較好,一個任務一個線程池,互不幹擾,TaskHelper.LargeTask.Run就是按這個思路寫的,不知道可有問題
附:
LimitedTaskScheduler代碼:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils { public class LimitedTaskScheduler : TaskScheduler, IDisposable { #region 外部方法 [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")] public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); #endregion #region 變數屬性事件 private BlockingCollection<Task> _tasks = new BlockingCollection<Task>(); List<Thread> _threadList = new List<Thread>(); private int _threadCount = 0; private int _timeOut = Timeout.Infinite; private Task _tempTask; public int ThreadCount { get { return _threadCount; } } #endregion #region 構造函數 public LimitedTaskScheduler(int threadCount = 10) { CreateThreads(threadCount); } #endregion #region override GetScheduledTasks protected override IEnumerable<Task> GetScheduledTasks() { return _tasks; } #endregion #region override TryExecuteTaskInline protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } #endregion #region override QueueTask protected override void QueueTask(Task task) { _tasks.Add(task); } #endregion #region 資源釋放 /// <summary> /// 資源釋放 /// 如果尚有任務在執行,則會在調用此方法的線程上引發System.Threading.ThreadAbortException,請使用Task.WaitAll等待任務執行完畢後,再調用該方法 /// </summary> public void Dispose() { _timeOut = 100; foreach (Thread item in _threadList) { item.Abort(); } _threadList.Clear(); GC.Collect(); GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); } } #endregion #region 創建線程池 /// <summary> /// 創建線程池 /// </summary> private void CreateThreads(int? threadCount = null) { if (threadCount != null) _threadCount = threadCount.Value; _timeOut = Timeout.Infinite; for (int i = 0; i < _threadCount; i++) { Thread thread = new Thread(new ThreadStart(() => { Task task; while (_tasks.TryTake(out task, _timeOut)) { TryExecuteTask(task); } })); thread.IsBackground = true; thread.Start(); _threadList.Add(thread); } } #endregion #region 全部取消 /// <summary> /// 全部取消 /// </summary> public void CancelAll() { while (_tasks.TryTake(out _tempTask)) { } } #endregion } }View Code
TaskHelper代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { /// <summary> /// Task幫助類基類 /// </summary> public class TaskHelper { #region UI任務 private static LimitedTaskScheduler _UITask; /// <summary> /// UI任務(4個線程) /// </summary> public static LimitedTaskScheduler UITask { get { if (_UITask == null) _UITask = new LimitedTaskScheduler(4); return _UITask; } } #endregion #region 計算任務 private static LimitedTaskScheduler _CalcTask; /// <summary> /// 計算任務(8個線程) /// </summary> public static LimitedTaskScheduler CalcTask { get { if (_CalcTask == null) _CalcTask = new LimitedTaskScheduler(8); return _CalcTask; } } #endregion #region 網路請求 private static LimitedTaskScheduler _RequestTask; /// <summary> /// 網路請求(32個線程) /// </summary> public static LimitedTaskScheduler RequestTask { get { if (_RequestTask == null) _RequestTask = new LimitedTaskScheduler(32); return _RequestTask; } } #endregion #region 資料庫任務 private static LimitedTaskScheduler _DBTask; /// <summary> /// 資料庫任務(32個線程) /// </summary> public static LimitedTaskScheduler DBTask { get { if (_DBTask == null) _DBTask = new LimitedTaskScheduler(32); return _DBTask; } } #endregion #region IO任務 private static LimitedTaskScheduler _IOTask; /// <summary> /// IO任務(8個線程) /// </summary> public static LimitedTaskScheduler IOTask { get { if (_IOTask == null) _IOTask = new LimitedTaskScheduler(8); return _IOTask; } } #endregion #region 大線程池任務 private static LimitedTaskScheduler _LargeTask; /// <summary> /// 大線程池任務(64個線程) /// </summary> public static LimitedTaskScheduler LargeTask { get { if (_LargeTask == null) _LargeTask = new LimitedTaskScheduler(128); return _LargeTask; } } #endregion } }View Code