多線程在項目開發過程中非常非常重要,這個系列就來詳細總結一下,首先認識一下多線程。 windows為什麼要支持多線程 電腦的早期時代,操作系統沒有線程的概念,整個系統只運行著一個執行線程,其中包含操作系統代碼和應用程式代碼。只用一個執行線程的問題在於,長時間運行的任務會阻止其他任務的執行。例如16 ...
多線程在項目開發過程中非常非常重要,這個系列就來詳細總結一下,首先認識一下多線程。
windows為什麼要支持多線程
電腦的早期時代,操作系統沒有線程的概念,整個系統只運行著一個執行線程,其中包含操作系統代碼和應用程式代碼。只用一個執行線程的問題在於,長時間運行的任務會阻止其他任務的執行。例如16位Windows的時代,列印文檔的應用程式很容易“凍結”整個機器。
Microsoft 在設計Windows NT這個版本的OS內核時,決定在一個進程中運行應用程式的每個實例。進程實際是應用程式的實例要使用的資源的集合。每個進程都被賦予了一個虛擬地址空間,確保一個進程中使用的代碼和數據無法由另一個進程訪問。這就確保了應用程式實例的健壯性。同時,進程訪問不了OS的內核代碼和數據;所以,應用程式代碼破壞不了操作系統的代碼和數據
如果應用程式發生死迴圈會發生什麼?如果機器只有一個CPU,它會執行死迴圈,不能執行其他任何程式。Microsoft 的解決方案就是線程。作為一個Windows概念,線程的職責是對CPU進行虛擬化。Windows為每個進程都提供了該進程專用的線程(功能相當於一個CPU)。應用程式的代碼進行死迴圈,與代碼關聯的進程會“凍結”,但其他進程(它們有自己的線程)不會凍結,它們會繼續執行。
線程很強大,因為它們使Windows即使在執行長時間運行的任務時,也能隨時響應。
所以多線程的發展歷史可以簡單總結為:沒有線程(只有一個執行線程)--->引入進程--->引入多線程
線程的開銷
線程是給我們帶來好處的同時,也有性能的損失,包括空間上和時間上的。
1,空間上
創建一個線程需要載入以下資源:
- 線程內核對象(thread kernel object),操作系統為系統中創建的每個線程都會分配並初始化這種數據結構,主要用於描述線程的屬性和線程上下文,上下文是一個記憶體塊,其中包含了CPU的寄存器集合。對於X86,X64和IA64的CPU來說,分別要使用700,1240和2500位元組的記憶體。
- 線程環境塊(thread environment block,簡稱TEB),TEB是在用戶模式(應用程式能快速訪問的記憶體地址)中分配和初始化的一個記憶體塊,TEB耗用1個記憶體頁(X86和X64 CPU中是4KB,IA64 CPU是8KB)。
- 用戶模式棧(user-mode stack),用戶模式棧用於存儲傳給方法的局部變數和實參,它還包含一個地址,指出當前方法返回時,線程接著應該從什麼地方執行,預設情況下,windows為每個線程的用戶模式棧分配1MB記憶體。
- 內核模式棧(kernel-mode stack),當應用程式代碼向操作系統中的一個內核模式的函數傳遞實參時,就會使用到內核模式棧。出於安全的考慮,Windowd會把這些實參從線程的用戶模式棧複製到線程的內核模式棧。32windows 內核模式棧大小12KB,64位是24KB。
- DLL線程連接(attach)和線程分離(detach)通知,Windows的一個策略是,任何時候在進程中創建線程,都會調用進程中載入的所有非托管DLL的DllMain方法,並向該方法傳遞DLL_THREAD_ATTACH標誌。同樣的,任何時候線程終止,都會調用進程中的所有非托管DLL的DllMain方法,並向該方法傳遞DLL_THREAD_DETACH標誌。
2,時間上
因為windows要在系統中的所有線程(邏輯CPU)之間共用物理CPU。在任何給定的時刻,windows只將一個線程分配給一個CPU,那個線程能運行一個“時間片”的長度。時間片到期,Windows就將上下文切換到另一個線程。
每個時間片的切換,windows都需要大概30ms的時間。
為什麼要使用多線程
1,可響應性,或稱用戶體驗,一般針對winform程式,可以將一些耗時的任務交給另一個線程去處理,使GUI線程能靈敏地響應用戶的輸入和操作。否則,界面會比較卡。
2,提升性能,由於windows每個CPU調度一個線程,多個CPU能並行調度線程,所以可以同時執行多個任務,從而提升性能。
進程,線程和應用程式域的關係
在進一步學習多線程之前,很有必要來瞭解一下這三個概念,以及其中的關係。
1,名詞解釋
進程
或稱Process,可以簡單理解為一個.exe的實例。進程是windows系統中的一個基本概念,它包含著一個運行程式所需要的資源。進程之間是相對獨立的,一個進程無法訪問另一個進程的數據(除非利用分散式計算方式),一個進程運行的失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作劃分為多個獨立的區域的。進程可以理解為一個程式的基本邊界。
線程
或稱Thread,可以簡單理解為虛擬CPU。線程是進程的基本執行單元,在進程入口執行的第一個線程被視為這個進程的主線程。在.NET應用程式中,都是以Main()方法作為入口的,當調用此方法時系統就會自動創建一個主線程。線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的記憶體與數據,TLS主要用於存放線程的狀態信息。
應用程式域
或稱AppDomain,可以簡單理解為一組程式集的邏輯容器。CLR在初始化在初始化時創建第一個AppDomain(預設AppDomain),這個AppDomain在進程終止時被銷毀。.NET的程式集正是在應用程式域中運行的。一個進程可以包含有多個應用程式域,一個應用程式域也可以包含多個程式集。
2,進程,線程和應用程式域的關係
可以用以下兩幅圖和兩句話來總結。
1),一個進程可以包含多個線程和應用程式域。
2),一個線程可以穿梭在多個應用程式域中,但在某個時刻,線程只會處於一個應用程式域內。
前臺線程和後臺線程的區別
1,前臺線程和後臺線程的區別在於,應用程式必須運行完所有的前臺線程才可以退出,而對於後臺線程,可以不考慮其是否運行完而直接退出並且不會拋出異常,所有的後臺線程在應用程式退出時就自動結束了。
2,預設情況下,主線程和使用Thread創建的線程都是前臺線程(使用線程池和Task創建的線程預設都是後臺線程),除非手動設置IsBackground= true。
多線程和非同步的區別
多線程和非同步在很多時候被認為是同一個東西,都是為了讓主線程不需要等待而繼續執行。
但是從辯證關係上來看,兩者還是有區別的,可以用一句話來概括。
非同步是目的,多線程是實現非同步的其中的一種方式(比如還可以通過創建另一個進程實現非同步)。