程序員的自我修養-線程詳解

以下內容摘自《程序員的自我修養》

什麼是線程?

線程(Thread),有時被稱爲輕量級(Lightweight Process, LWP),是程序執行流程的最小單元。一個標準的線程由線程ID、當前指令指針(PC)、寄存器集合和堆棧組成 。通常意義上,一個進程由一個到多個線程,各個線程之間共享程序的內存空間(包括代碼段、數據段、堆等)及一些進程級的資源(如打開文件和信號)。一個經典的線程與進程的關係如圖:

1620uploading.4e448015.gif轉存失敗重新上傳取消

大多數軟件應用中,線程的數量都不止一個。多個線程可以互不干擾地併發進行,並共享進程的全局變量和堆的數據。那麼,多個線程與單線程的進程相比,又有哪些優勢呢?通常來說,使用多線程的原因有如下幾點:

  • 某個操作可能會陷入長時間等待,等待的線程會進入睡眠狀態,無法繼續執行。多線程執行可以有效利用等待的時間。
  • 某個操作(常常是計算)會消耗大量的時間,如果只有一個線程,程序和用戶之間的交互會中斷。多線程可以讓一個線程負責交互,另一個線程負責計算。
  • 程序邏輯本身要求併發操作,例如一個多端下載軟件
  • 多CPU或多核計算機,本身具備同時執行多個線程的能力,因此單線程程序無法全面發揮計算機的全部計算能力。
  • 相對於多進程應用,多線程在數據共享方面的效率高得多。

線程訪問權限

線程的訪問非常自由,它可以訪問進程內存裏的所有數據,甚至包括其他線程的堆棧(如果它知道其他線程的堆棧地址,那麼就是很少見的情況),但實際運用中線程也擁有自己的私有存儲空間,包括以下幾方面:

  • 線程局部存儲
  • 寄存器

線程調度與優先級

不論是在多處理器的計算機上還是單處理器的計算機上,線程總是“併發”執行的。當線程的數量小於等於處理器的數量時(並且操作系統支持多處理器),線程的併發是真正的併發,不同的線程運行在不同的處理器上,彼此之間互不相干。但對於線程數量大於處理器的情況,線程的併發會受到一些阻礙,因此此時至少一個處理器會運行多線程。 在單處理器對應多線程的情況下,併發是一種模擬出來的狀態。操作系統會讓這些多線程程序輪流執行,每次僅執行一小段時間,(通常是幾十到幾百毫秒),這樣每一個線程就“看起來”在同時執行。這樣的一個不斷在處理器上切換不同的線程的行爲稱之爲線程調度。在線程調度中,線程通常擁有三種狀態,分別是:

  • 運行(Running)
  • 就緒(Ready)
  • 等待(Waiting) 處於運行中的線程擁有一段可執行的時間,這段時間稱爲時間片。當時間片用盡後的時候,進程就開始等待某事件,那麼它將進入等待狀態。每當一個線程離開運行狀態時,調度系統就會選擇一個其他的就緒線程繼續執行。在一個處於等待狀態的線程鎖等待的事件發生之後,該線程就將進入就緒狀態;

1620uploading.4e448015.gif轉存失敗重新上傳取消

線程調度自多任務操作系統問世以來,就不斷被提出不同的方案和算法,現在主流的調度方式儘管各不相同,但都帶有優先級調度和輪轉法。 在windows中可以通過

BOOL WINAPI SetThreadPriority(HANDLE hThread, int nPriority);

來設置線程的優先級,而linux下與線程相關的操作可以通過pthread庫來實現;

讓我們總結一下,在優先級調度的環境下,線程的優先級改變一般有三種方式。

  • 用戶指定優先級
  • 根據進入等待狀態的頻繁程序提升或降低優先級
  • 長時間得不到執行而被提升優先級

可搶佔線程和不可搶佔線程

我們之前討論的線程調度有一個特點,那就是線程在用盡時間片之後會被強制剝奪繼續執行的權利,而進入就緒狀態,這個過程叫做搶佔。 在不可搶佔的線程中,線程主動放棄執行無非是兩種情況。

  • 當線程試圖等待某一事件時(I/O等)。
  • 線程主動放棄時間片 因此,在不可搶佔線程執行時候,有一個顯著的特點,那就是線程調度的時間是確定的。線程調度只會發生在線程主動放棄執行或線程等待某事件的時候。這樣可以避免一些因爲搶佔式線程裏調度時間不確定而產生的問題。

Linux的多線程

Windows對進程和線程的實現如同教科書一般標準,windows內核有明確的線程和進程的概念,並且有一系列的API來操縱它們。但對於linux來說,線程並不是一個通用的概念。 Linux對多線程的支持頗爲貧乏,事實上,在Linux內核中並不存在真正意義上的線程概念。Linux將所有的執行實體(無論是線程還是進程)都稱爲任務(Task),每一個任務概念上不同任務之間都可以選擇共享空間,因此在實際意義上,共享同一個內存空間的多個任務構成了一個進程,這些任務也就成了這個進程中的線程。在Linux下,用以下方法可以創建一個新的任務:

1620uploading.4e448015.gif轉存失敗重新上傳取消

fork函數產生一個和當前進程完全意義的新進程,並和當前進程一樣從fork函數裏返回。例如如下代碼:

pid_t pid;
if (pid = fork())
{
    ....
}

fork只能夠產生本任務的鏡像,因此須要使用exec配合才能啓動別的新任務。exec可以用新的可執行映像替換當前的可執行鏡像

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章