線程相關概念
在學習多線程之前,先來了解下幾個與多線程相關的概念。
進程:進程是計算機的概念,程序在服務器運行時占據全部計算資源的總和,一個應用程序運行起來就是一個進程,打開windows的任務管理器,如下圖
線程:線程也是計算機的概念,線程是進程的最小單位,也是程序在響應操作系統時的最小單位,一個進程至少由一個線程(主線程)構成。線程和進程一樣也會占據一定的CPU、內存、網絡、硬盤IO等。一個線程隸屬於某個進程,進程銷毀,線程也隨之銷毀。
句柄:是一個long類型的數字,是操作系統用來標識應用程序的,有點主鍵或者身份證號碼的意思。
多線程:一個進程或者說一個應用程序有多個線程在運行參與計算。
C#里面的多線程
Thread類是C#語言對線程對象的封裝。在.netframework1.0開始出現。在后面的多線程系列文章中會講到在不同的.netframework版本中多線程的API使用,在本篇文章中,先來初步認識多線程。
為什么可以使用多線程
1:CPU的多核技術和模擬核技術:
如計算機的參數概念4核8線程,所謂的4核8線程,4核指的是物理核心。通過超線程技術,用一個物理核模擬兩個虛擬核,每個核兩個線程,總數為8線程。四核八線程采用的超線程技術,是指每個CPU核心沒有滿負荷運載時,其剩余用量可以模擬成虛擬的核心。單個物理核同一時間點只能處理一個線程,通過超線程技術可以實現單個物理核實現線程級別的並行計算。
2:CPU分片:實際上CPU在同一時刻只能處理一個任務,但是因為CPU的計算能力強大,在1秒內可以響應不同的任務,把1秒的處理能力分成10份,1到100毫秒處理任務A,101到200毫秒處理任務B,201到300毫秒處理任務C…,從宏觀角度來看,感覺多個任務在並發執行,這個就是CPU的分片。
初識同步和異步
同步方法:發起調用,完成后才繼續下一行;非常符合開發思維,有序由上至下執行;
異步方法:發起調用,不用等待完成,直接進入下一行,啟用一個新的線程來完成計算。
同步方法就像真誠的請人吃飯,客人說他有事,需要忙一會兒,請吃飯的人等待客人忙完了再一起吃飯。異步方法就像客氣的請人吃飯,客人說他有事,請吃飯的人說那你先去忙吧,然后自己就去吃飯了。
同步和異步的比較
同步方法卡界面,主線程(UI)線程忙於計算,無暇他顧,異步方法不卡界面:主線程閑置,計算任務交給子線程完成,改善用戶體驗。如在winform中點擊按鈕采用同步的方式調用一個復雜的任務計算會導致界面短暫卡死,直到任務計算結束才可以操作界面。
在web應用中發個短信通知,記錄一個日志,都可以采用異步的方式去執行,客戶端不用等到短信發送成功或者日志記錄成功才能接受到服務端的響應。
為了能夠清楚的說明情況,這里采用測試程序對比的方式,測試程序界面如下:
計算任務:
private void DoSomeThing(string btnName) { Console.WriteLine($"{btnName} 開始,當前線程id:{Thread.CurrentThread.ManagedThreadId}"); long lResult = 0; for (long i = 0; i < 1_000_000_000; i++) { lResult += i; } Console.WriteLine($"{btnName} 結束,當前線程id:{Thread.CurrentThread.ManagedThreadId}"); }
同步方式調用:
private void BtnSync_Click(object sender, EventArgs e) { Console.WriteLine($"btnSync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")}" + $" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < 5; i++) { string name = string.Format($"btnSync_Click_{i}"); this.DoSomeThing(name); } stopwatch.Stop(); Console.WriteLine($"同步方法耗時:{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"DoSomeThing End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); }
同步方式調用執行結果:
同步方式調用時CPU的使用情況:
異步方式調用:
private void BtnAsync_Click(object sender, EventArgs e) { Console.WriteLine($"btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")}" + $" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); Stopwatch stopwatch = new Stopwatch(); List<Task> tasks = new List<Task>(); stopwatch.Start(); for (int i = 0; i < 5; i++) { int k = i; tasks.Add(Task.Run(()=> { string name = string.Format($"btnAsync_Click{k}"); this.DoSomeThing(name); })); } Task.Run(()=> { Task.WaitAll(tasks.ToArray()); stopwatch.Stop(); Console.WriteLine($"異步方法耗時:{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"DoSomeThing End {Thread.CurrentThread.ManagedThreadId.ToString("00")}" + $" {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); }); }
異步方式調用執行結果:
異步方式調用時CPU的使用情況:
總結
同步方法慢,上圖耗時(16402毫秒),因為只有一個線程計算。異步方法快,上圖耗時(10524毫秒),因為有多個線程參與計算。觀察同步和異步調用時的使用情況折線圖分析得知:多線程其實就是資源換取性能。在一個應用程序中是不是開啟的線程越多越好?不是的,因為開啟更多的資源,需要消耗更多的計算機資源,資源是有限的、資源調度也需要消耗資源,就像項目經理管理開發人員保證項目進度,項目經理調度(管理)開發人員也是需要工資的。
一個訂單表統計很耗時間,能用多線程優化性能么?不能!這操作只包含一個任務,沒辦法並行計算,就像一個老師不能同時在兩個班級講課。如果一個操作在查詢數據庫的同時,需要調用接口、讀寫硬盤文件、做數據計算,這個可以用多線程優化性能,因為多個任務可以並行計算。
同步方法有序進行,異步多線程無序
啟動無序:線程資源是屬於非托管資源,是程序向操作系統申請的,由操作系統的調度策略決定,所以啟動順序是隨機的,cpu使用同一個線程計算同一個任務,執行時間也是不確定的,so,結束也是無序的。在使用多線程的時候一定要小心,尤其是多線程間有順序要求的時候通過延遲一點時間(Thread.Sleep())來控制執行順序,這是不靠譜的。下一篇我們繼續學習C#里面的多線程 多線程系列(二)之Thread類