天天看點

多線程程式設計(一)-- 揭開線程的神秘面紗

一、windows為什麼要支援線程

  早期作業系統的狀況:早期,作業系統沒有線程的概念。整個系統隻運作着一個執行線程,其中同時包含作業系統代碼和應用程式代碼。

  問題:長時間運作的任務會阻止其他任務的執行。某個應用程式的執行可能會當機整個機器,造成OS和其他應用程式停止響應。如果應用程式有bug,會造成無線循環,同樣會造成整個機器停止工作,并且會導緻資料丢失。

  改進:建構一個新的OS來滿足企業和個人的需要。

                 1、MS在設計這個OS的核心時。決定在一個程序中運作應用程式的每個執行個體。程序是應用程式的一個執行個體要使用的資源的一個集合。每個程序都被賦予了一個虛拟的位址空間,確定一個程序使用的代碼和資料無法由另一個程序通路。這就確定了應用程式執行個體的健壯性,因為一個程序無法破壞另一個程序使用的代碼和資料。除此之外,OS的核心代碼和資料是程序通路不到的,是以應用程式破壞不了作業系統的代碼和資料。

                 2、但是CPU本身呢?如果一個應用程式進入無限循環,并且機器中隻有一個CPU,那他會執行無限循環,不能執行其他任何東西。是以,系統還是可能會停止響應。MS對于這個問題拿出的解決方案是線程。線程的職責是對CPU進行虛拟化。Windows為每個程序都提供了該程序專用的線程(功能相當于一個CPU,可将線程了解為一個邏輯CPU)。如果應用程式的代碼進入無線循環,與那個代碼關聯的程序會“當機”,但其他的程序還會繼續執行。

二、線程開銷

  和一切虛拟化機制一樣,線程會産生空間(記憶體耗用)和時間(運作時的執行性能)上的開銷。

  每個線程中,都有以下要素:

  1、線程核心對象。

  2、線程環境塊。

  3、使用者模式棧。

  4、核心模式棧。

  5、DLL線程連結和線程分離通知。

  建立和終止線程引起的開銷:任何時候在程序中建立一個線程,都會調用那個程序中加載的所有DLL的DllMain方法,并向該方法傳遞一個DLL_THREAD_ATTACH辨別。類似的,任何時候一個線程終止,都會調用程序中的所有DLL的DllMain方法,并向該方法傳遞一個DLL_THREAD_DETACH辨別。(注:C#和其他大多數的程式設計語言生産的DLL沒有DLLMain函數,是以托管DLL不會收到DLL_THREAD_ATTACH和DLL_THREAD_DETACH的通知,這提升了性能。)

  上下文切換引起的開銷:單CPU的計算機一次隻能做一件事情。是以Windows必須在系統中的所有線程(邏輯CPU)之間共享實體CPU。在任何給定的時刻,Windows隻将一個線程配置設定給一個CPU,那個線程允許運作一個“時間片”,一旦時間片到期,Windows就上下文切換到另一個線程,每次上下文切換都要求Windows執行以下操作:

  1、将CPU寄存器中的值儲存到目前正在運作的線程的核心對象内部的一個上下文結構中。

  2、從現有的線程集合中選出一個線程供排程。如果該線程有由另一個程序擁有,Windows在開始執行任何代碼或者接觸任何資料之前,還必須切換CPU“看見”的虛拟位址空間。

  3、将所選上下文結構中的值加載到CPU的寄存器中。

  Windows大約每30毫秒執行一次上下文切換。上下文切換是淨開銷:也就是說上下文切換所産生的開銷不會換來任何記憶體或者性能上的收益。Windows執行上下文切換,向使用者提供一個健壯的、響應靈敏的作業系統。執行上下文切換所需要的時間取決于CPU架構和速度。而填充CPU緩存所需時間取決于系統中運作的應用程式、CPU緩存的大小及其他各種因素。是以無法為每一次上下文切換的時間開銷給出一個确定的值,甚至無法給出一個估計的值。唯一确定的是,如果要建構高性能的應用程式群組件,就應該盡可能的避免上下文切換。

  在執行垃圾回收時,CLR必須挂起(暫停)所有線程,周遊他們的棧來查找跟以便對對堆中的對象進行标記,再次周遊他們的棧(對象在壓縮期間可能發生了移動,是以要更新他們的根),再恢複所有線程。是以,減少線程的數量會限制提升垃圾回收器的性能。

  未完待續.........