文章出處

進程:運行應用程序實例
線程:對CPU進行虛擬化。windows為每個進程都提供了該進程專用的線程,這樣當一個進程“假死”,不會影響到其他進程。

線程開銷

線程可以使windows在長時間運行任務可以隨時得到響應,允許用戶使用一個應用程序將已凍結的應用程序強制結束。

  • 線程內核對象(thread kernel object)
    操作系統為系統中創建的每個線程都分配并初始化的數據結構之一。該數據結構包含了線程上下文(線程上下文實際上是內存塊,中包含了CPU指令集)
  • 線程環境塊(thread environment block,TEB)
    在用戶模式中分配和初始化的內存塊。TEB耗用一個內存頁(x86和x64 CPU是4K,IA64 CPU是8k)
  • 用戶模式棧(user-mode stack)
    用于存儲傳給方法的局部變量和實參。它還包含一個方法返回時線程從什么地方開始執行的地址。windows默認為用戶模式棧分配1M內存
  • 內核模式棧(kernel-mode stack)
    出于安全原因,針對從用戶模式的代碼傳遞給內核的任何實參,Windows會把它們從線程的用戶模式棧復制到線程的內核模式棧。一經復制,內核就可驗證實參的值。應用程序無法訪問內核模式棧,所以驗證后的值是無法修改的。
  • DLL線程連接(attach)和線程分離(detach)通知
    任何時候在進程中創建線程或線程終止,都會調用DllMain方法(創建傳遞DLL_THREAD_ATTACH標志,終止傳遞DLL_THREAD_DETACH標記)

上下文切換

單CPU計算機一次只能做一件事,所以Windows必須在系統中的所有線程(邏輯CPU)之間共享物理CPU。在任何一刻,Windows只將線程分配給一個CPU,該線程允許運行一個“時間片”(也稱為“量”或者"量程",即quantum)。一旦時間片到期,Windows的上下文就切換到另個一個線程。

  1. 將CPU寄存器中的值保存到當前正在運行的線程的內核對象內部的一個上下問的結構中
  2. 從現有線程集中選出一個線程供調度,如果該線程由另一個進程擁有,Windows開始執行代碼或接觸任何數據之前,還必須切換CPU“看見”的虛擬地址空間
  3. 將所選上下文結構中的值加載到CPU寄存器中
    Windows大約30毫秒執行一次上下文切換。上下文切換是凈開銷(即開銷不會換來任何性能的提升)。當一個應用程序的線程進入無限循環,Windows會頂起搶占(preempt)它,將一個不同線程分配給CPU,讓新的線程運行一會。
    上下文切換性能影響
    CPU要執行一個不同的線程,而之前的線程的代碼和數據還在CPU的緩存中,這使CPU不必經常訪問RAM(RAM的速度要比CPU的高速緩存慢得多),當Windows上下文切換到一個新的線程時,這個新的線程極有可能要執行不同的代碼并訪問不同的數據,這些代碼和數據并不在CPU的高速緩存中,因此,CPU必須訪問RAM來填充它的告訴緩存,以恢復高速執行的狀態,但30毫秒后,新的一輪上下文切換又開始了。
    切換的所需的時間是由CPU架構和速度決定的。而填充CPU緩存的時間取決于系統中運行的程序、CPU緩存大小等因素。所以無法為每次上下文切換的時間開銷確定一個值。如果要構建高性能的應用程序和組件,應盡可能的避免上下文切換。

一個時間片結束,如果Windows決定再次調度同一線程(而不是切換到另一線程),那么Windows不會執行上下文切換。設計代碼時,上下文切換能避免盡量避免。雖然說避免使用線程(會造成消耗大量內存,浪費時間創建、銷毀和管理線程),但使用線程也使Windows變得更加穩定。

安裝多個CPU(或多核CPU)的計算機可以真正實現同時運行多個線程,這提升了應用程序的可伸縮性(并發執行多任務)。

資源浪費

在Windows中,相對于線程的創建,創建一個進程通常需要幾秒,必須分配大量的內存(從磁盤中加載DLL和exe文件等等操作),所以一般開發人員都選擇創建線程。這就是為什么我們的電腦上一般只開了幾十個程序(進程),但是有上千個線程的原因。雖然線程比進程廉價,但是和其他系統資源相比忍讓非常昂貴。之前我們已經算過,創建一個線程至少1M多,如果1000個線程就是1G多。打開任務管理器,我們可以看到很多程序暫用了大量的內存,但是他們的CPU使用率幾乎為0%。

創建一個專用的線程

創建線程執行異步操作,一般推薦通過CLR的線程池來執行操作。但是滿足以下幾點的話,也可以顯示創建一個線程來執行異步操作。

  • 線程需要非普通線程優先級運行;線程池的所有線程都是以普通優先級運行。
  • 需要線程表現為一個前臺線程,防止應用程序在線程結束它的任務前終止。
  • 需要長時間運行
  • 要啟動一個線程,并可能調用Thread的Abort方法來提前終止它
   System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart((o) =>
   {
       System.Threading.Thread.Sleep(500);
       Console.WriteLine("Hello 線程1");
   }));
   thread.Start();
   Console.WriteLine("Hello 線程2");

使用線程的理由

  • 可以使用線程代碼同其他代碼隔離
  • 可以使用線程簡化編碼
  • 可以用線程來實現并發執行

今天,CPU的成本越來越低,計算機使用的CPU核心越來越多;所以軟件應該大膽的去利用硬件,比如Visual Studio的編輯器停止編輯的時候,Visual Studio會自動編譯代碼,這樣極大的提高了開發著的效率,傳統的“編輯代碼-編譯生成-調試”模式逐漸被“編輯代碼-調試”模式取代,因為有大量CPU能力提供使用,編譯器的頻繁運行不會影響到其他事情。

線程調度和優先級

Windows之所以被稱為一種搶占式多線程操作系統,是因為線程可以在任何時間被停止(搶占),并調度另一個線程。用戶有一定的控制權,但是并不能保證自己的線程一直運行,并且不能阻止其他線程運行。
線程的優先級從0(最低)~31(最高),系統決定將哪一個線程分配給CPU時,先檢查優先級31的線程,并以輪流的方式調用他們。只要存在可調度的優先級為31的線程,系統就永遠不會將優先級0~30的線程分配給CPU,這種情況稱為饑餓(starvation)。
系統啟動時,會創建優先級為0的零頁線程,零頁線程的作用是沒有其他進程需要執行的時候將系統RAM空閑頁清零。
在設計程序時,應決定自己的應用程序是比機器上運行的其他應用程序更大還是更小的影響力。然后選擇一個優先級類,Windows支持6個進程優先級類:Idle,Below Normal,Normal,Above Noraml,High,Reatime。進程的優先級類默認值一般為Noraml。只有絕對必要的時候才使用High優先級,特別是Reatime,等級越高會直接影響到操作系統的某些任務。
Windows支持7個相對線程優先級:Idle,Lowest,Below Normal,Normal,Above Noraml,Highest,Reatime-Critical。這些優先級是相對于進程優先級類的。可以將進程看作是優先級類的成員,在進程中要為線程分配優先級。

前臺線程和后臺線程

CLR將每個線程要么視為前臺線程,要么是后臺線程。一個進程的所有前臺線程停止運行時,CLR強制終止仍然在運行的后臺線程(且不會拋出異常)。
在一個線程的生存期中,任何時候都可以由前臺線程變為后臺線程,或者從后臺變為前臺。應用程序的主線程和通過構造一個Thread對象顯示創建的任何線程默認為前臺線程,線程池線程默認為后臺線程。
推薦使用CLR的線程池,它自動可以管理線程的創建、銷毀。并重用已創建的線程,避免了不必要的開銷。


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()