多線程編程--線程基礎(一)


      都說操作系統是用戶體驗驅動其發展的,在很久很久的Micrisoft16Windows操作系統中,那是單線程而且是不能搶占的CPU的操作系統,這樣導致了當某個線程發生死鎖或者不能正確的運行的時候,整個操作系統都不能運行,處於一種凍結的狀態。用戶只能無奈的按下Reset按鈕來進行重啟。這樣會導致之前運行的所有的數據都會丟失。因此,新的內核就被設計出來了。--我只是知識的搬運工

  在新的OS內核中,進程實際是應用程序的實例要使用的資源的集合。每個進行都被賦予了一個虛擬地址空間,確保在一個進程中使用的代碼和數據不會被另外一個進行所訪問。而線程的職責是對CPU進行邏輯的虛擬化。

線程的開銷                                                                                                                                                     

  在創建,銷毀一個線程的過程中,存在着一些巨大的開銷。這些開銷有時間上,也有空間上面的。

 線程內核對象(Thread-kernel object

  這種數據結構包含着一組對線程進行描述的屬性,這些屬性一般是用於CPU的調度,例如線程的優先級,等待的時間等等。同時還包括了線程的上下文(Thread Contexgt),這是CPU寄存器集合的內存塊所存放的信息的副本。

 線程環境塊(thread environment block

  TEB是在用戶模式中分配的初始化的內存塊。TEB耗用一個內存頁(4KB)。TEB包含着異常處理鏈首。線程進入的每個try塊都在鏈首插入一個節點;線程推出try塊是,從鏈表中刪除該節點。TEB還包含着線程的“線程本地存儲”數據,以及由GDIOpenGL圖形使用的一些數據結構。

用戶模式棧(User-model stack

  這就是常說的用戶棧,用於存放傳給方法的形參和方法中自定義的實參,已經當前方法返回的時候,線程應該從那個地方開始執行。Windows的默認用戶模式棧的大小為1M。

內核模式棧

  當應用程序代碼想操作系統中的內核模式函數傳遞實參時,還會使用內核模式棧。出於對安全的考慮,所有由應用程序向內核函數傳遞的參數,都會先復制到內核模式棧中。由於程序不能訪問內核模式棧,所以參數一經復制過去內核模式棧中,程序便不能修改其值。其實這個內核模式棧在功能上跟用戶模式棧的作用是一樣的,都是用於存儲傳給方法的形參,已經方法中自定義的實參,以及函數的返回地址。特別之處就是,應用程序不能修改里面的值,只能由內核函數進行修改,從而達到了安全。

DLL線程連接和線程分離通知

  上面所說的三個開銷都是對於內存的開銷,這個DLL線程連接和線程分離通知卻是時間上面的開銷。不過這也是相對的來說的,因為上面的三個開銷,在分配內存,初始化內存過程中,也必須花費很多的時間。在創建一個新的線程的時候,Windows都會調用進程中加載的所有非托管DLLDllMain方法,並向這個方法傳遞一個DLL_THREAD_ATTCH標志。類似的,終止線程的時候,也會調用這個DllMain方法,並傳遞一個DLL_THREAD_DETACH標志。因為有些DLL需要獲取這些通知,才能為進程中創建/銷毀的每個線程執行特殊的初始化或者清理操作。事實上,每個進程都會加載很多非托管的DLL文件,所以初始化或者銷毀一個線程,便需要調用多個的DllMain方法。

 

CPU調度時上下文切換的開銷                                                                                                                      

  上下文切換的開銷主要集中在兩個方面:

  • 把一個線程從CPU中移除,然后根據一定的調度算法,用分派器選擇某個線程,在上下文切換器中進行切換。
  • 如果該切換的線程的代碼和數據還在RAM中,就必須從RAM中讀取數據到CacheCPU寄存器中。

 

不使用ThreadPool而自己創建一個線程的原則                                                                                                 

         一般情況下,要為不會阻止其他線程的相對較短的任務處理多個線程並且不需要對這些任務執行任何特定調度時,使用 ThreadPool 類是一種最簡單的方式。 但是,有多個理由創建您自己的線程:

  • 如果您需要使一個任務具有特定的優先級。
  • 如果您具有可能會長時間運行(並因此阻止其他任務)的任務。
  • 如果您需要將線程放置到單線程單元中(所有 ThreadPool 線程均處於多線程單元中)。
  • 如果您需要與該線程關聯的穩定標識。 例如,您應使用一個專用線程來中止該線程,將其掛起或按名稱發現它。
  • 如果您需要運行與用戶界面交互的后台線程,.NET Framework 2.0 版提供了 BackgroundWorker 組件,該組件可以使用事件與用戶界面線程的跨線程封送進行通信。

 

 線程的優先級                                                                                                                                              

  Windows中,線程的優先級是從0(最低)-31(最高)的。一般CPU是根據線程的優先級來調度線程的。如果當前調度的線程的線程的優先級是10,如果突然線程的就緒隊列中,來了一個優先級為15的線程,那么操作系統會強制的進行線程的切換,來馬上調度優先級為15的線程,這被稱為搶占式調度。

  在實際編程中,我們是看不到這0-31的優先級的,這是因為Windows只是公開了這些優先級的一個抽象。特別要說明的的是,線程的優先級由進程的優先級類和線程的相對優先級來決定。

 

進程優先級類

 

 

 

 

 

相對線程優先級

Idle

Below Normal

Normal

Above Normal

High

Realtime

Time-Cirtical

15

15

15

15

15

31

Highest

6

8

10

12

15

26

Above Normal

5

7

9

11

14

25

Normal

4

6

8

10

13

24

Below Normal

3

5

7

9

12

23

Lowest

2

4

6

8

11

22

Idle

1

1

1

1

1

16

 

  這里要特別說明的是,Windows為自己保留了優先級0Realtime范圍,同時CLR也為自己保留了IdleTime-Cirtical范圍的優先級。程序的線程絕對優先級是由進程優先級類和相對線程優先級所共同決定的,同時我們不應該去更改進程的優先級。因為在正常情況下,進程根據其啟動它的進程來分配優先級。大多數進程都是由Windows資源管理器啟動,后者在Normal優先級類中生成他們的所有子進程。所以我們在Thread.Priority中只有Highest,Above Normal,Noraml,Below Normal,Lowset這幾種,相對應的優先級是678910

 spy++軟件查看Chrome的某個線程spy++軟件查看Chrome的某個線程

圖1 Spy++中查看Chrome的某個線程的信息

前台線程和后台線程                                                                                                                                    

  前台線程和后台線程的主要區別是,進程中只要存在沒有完成的前台線程,進程就不會被銷毀。換句話說就是,如果所有的前台線程都完成了,進程就會被銷毀,即使是存在未完成任務的后台線程。我們可以通過設置Thread.IsBackground來把線程設置為前台線程或者是后台線程。通過ThreadPool(其中ThreadPool.QueueUserWorkItemTimer,Task等等都是通過ThreadTool來實現的)來實現的線程都是后台線程,通過Thread類來實現的線程都是前台線程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM