概述
幾乎所有現代操作系統都允許一個進程包含多個線程。
每個線程是CPU使用的一個基本單元。它包括線程ID,程序計數器,寄存器組和堆棧。
與同一進程其他線程共享代碼段,數據段和其他操作系統資源。
*在同一進程的多線程之間,哪些程序狀態部分會被共享?
堆內存,全局變量等。
線程獨有的寄存器值與寄存內存等
多線程編程具有下面四大類優點:
- 響應性:如果一個交互程序採用多線程,那麼即使部分阻塞或者進行冗長操作,它仍可以繼續執行,增加對用戶的響應程序。
- 資源共享:進程只能通過共享內存或消息傳遞的技術共享資源,而線程默認共享它們所屬進程的內存和資源。可以減少通信的消耗。
- 經濟:進程創建所需的內存和資源分配非常昂貴。而線程能夠共享所屬進程的內存和資源,所以創建和切換線程更加經濟。
- 可伸縮性:對於多處理器體系結構,多線程的優點更大。因爲線程可在多處理核上並行運行。
*舉兩個多線程不比單線程性能好的實例?
任何形式的順序程序
還有shell類程序,因爲它必須密切檢測其本身的工作空間。
*在處理器系統上採用多個用戶級線程比在單處理器系統的單線程,提供更好的性能嗎?
不能。
一個包括多用戶線程的多線程系統無法在多處理器系統上同時使用不同的處理器。操作系統只能看到單一的進程且不會調度在不通過處理器上的不同進程的線程。
因此,多處理器系統執行多個用戶線程沒有性能優勢。
多核編程
多核(multicore)和多處理器(multiprocess)系統:無論多個計算核是在多個CPU芯片上還是在單個CPU芯片上,都成爲多核或多處理器系統。
多核系統編程有五個方面的挑戰:
- 識別任務:分析應用程序,查找區域以便分爲獨立的,併發的任務。
- 平衡:在識別可以並行運行任務相比,還應確保任務執行同等價值的工作。有些情況下,貢獻少的任務也單獨佔用一個內核來工作,這樣就不值得了。
- 數據分割:不僅任務要分割,數據也需要分割成單獨的部分。
- 數據依賴:任務訪問的數據必須分析多個任務之間的依賴關係。
- 測試與調試:測試與調試比單線程的應用程序更加困難。
並行類型
通常,有兩種類型的並行,數據並行(data parallelism)與任務並行(task parallelism)
- 數據並行注重將數據分佈於多個計算核上。
- 任務並行涉及將任務分配到多個計算核上,每個線程都執行一個獨特的操作。不同線程可以操作相同數據,也可以操作不同數據。
多線程模型
有兩種不同方法提供線程支持,用戶層的用戶線程(user thread)和內核層的內核線程(kernel threrad)
用戶線程位於內核之上,管理無需內核支持,而內核線程由操作系統直接支持與管理。
用戶線程與內核線程存在對應關係,如多對一模型,一對一模型與多對多模型。
多對一模型
多對一模型映射多個用戶線程到一個內核線程。
如果一個線程執行阻塞系統調用,那麼整個進程都會被阻塞。又因爲任一時間只有一個線程可以訪問內核,所以多個進程不能並行運行到多處理器系統上。
一對一模型
一對一模型映射每個用戶線程到一個內核線程。
該模型在一個線程執行阻塞系統調用時,能允許另一個線程繼續執行,所以它提供了更好的併發功能,
它也允許多個線程並行運行在多處理器系統上。
唯一的缺點是,創建一個用戶線程就要創建一個相應的內核線程,這會增加開銷,影響應用程序的性能。
多對多模型
多對多模型,多路複用多個用戶級線程到同樣數量或更少數量的內核線程。
內核線程的數量可能與特定應用程序或特定機器有關
開發人員可以創建任意多的用戶線程,並且相應內核線程能在多處理器系統上併發執行。而且,當一個線程執行阻塞系統調用,內核可以調度另一個進程來執行。
雙層模型
多對多模型的一種變種仍然多路複用多個用戶級線程到同樣數量或更少數量的內核線程,但也允許綁定某個用戶線程到一個內核線程。
線程庫
線程庫(thread library)爲程序員提供創建和管理線程的API。
實現線程庫的主要方法有兩種:
- 第一種方法:在用戶空間中提供一個沒有內核支持的庫。調用庫內的一個函數只是導致了用戶空間內的一個本地函數的調用,而不是系統調用。
- 第二種方法:實現由操作系統直接支持的內核級的一個庫。調用庫中的一個API函數通常會導致對內核的系統調用
目前使用的三種主要線程庫是:POSIX Pthreads, Windows, Java.
隱式多線程
隨着多核處理的日益增多,出現了擁有數百甚至數千線程的應用程序。設計這樣的應用程序不起一個簡單的事情,所以,爲了解決這些困難並且更好支持設計多線程程序,出現一種隱式策略(implicit threading):
有一種方法是將多線程的創建與管理交給編譯器和運行時庫來完成。
線程池
先舉一個例子,假設服務器每收到一個請求就會創建一個單獨線程來處理請求。這樣就會有潛在的問題。
第一個問題就是創建線程也是需要時間,第二個問題更爲嚴重,如果允許所有併發請求都通過新進程來處理,那麼我們沒有限制系統內的併發執行線程的數量。無限制的線程可能耗盡系統資源,如CPU時間和內存。
這時候就可以使用線程池(thread tool)。
線程池的主要思想是:在進程開始時創建一定數量的線程,並加到池中以等待工作。當服務器收到請求時,它會喚醒池內的一個線程(如果有可用線程),並將需要服務的請求傳遞給它。一旦線程完成了服務,就會回到池中再等待工作。如果池內沒有可用線程,那麼服務器就會等待,知道有空線程爲止。
線程池有以下三個優點:
- 用現有線程服務請求比等待創建一個線程更快
- 線程池限制了任何時候可用線程的數量。
這對那些不能支持大量併發線程的系統非常重要
- 將要執行任務從創建任務的機制中分離出來,允許我們採用不同策略運行任務。
OpenMP
OpenMP爲一組編譯指令和API,用於編寫C,C++,Fortran等語言的程序。它支持共享內存環境下的並行編程。
OpenMP識別並行區域(parallel region),即可並行運行的代碼塊。
大中央調度
大中央調度(Grand Central Dispatch ,GCD),是Apple Mac OS X和iOS操作系統的一種技術。
它允許應用程序開發人員將某些代碼區段並行運行。
多線程問題
本節討論多線程出現的一些問題
信號處理
UNIX信號(signal)用於通知進程某個特定事件已經發生。
信號的接收可以是同步的,也可以是異步的。
不管怎樣,所有信號,都遵循相同的模式:
- 信號是由特定事件的發生而產生的。
- 信號被傳遞給某個進程。
- 信號一旦收到就應處理。
- 信號處理程序分爲兩種:
缺省的信號處理程序
用戶定義的信號處理程序
每個信號都有一個缺省信號處理程序(default signal handler),在處理信號時,由內核來運行。
這種缺省動作可以通過**用戶定義處理程序(user-defined signal handler)**來改寫。
如果一個進程有多個線程,那麼信號應被傳遞到哪裏去呢?一般有以下四種選擇:
- 傳遞信號到信號所適用的線程
- 傳遞信號到進程內的每個線程
- 傳遞信號到進程內的某些線程
- 規定一個特殊線程接受該進程的所有信號
線程撤銷
線程撤銷(thread cancellation) 是在線程完成之前終止線程。
如用戶按下網頁瀏覽器的按鈕,以停止進一步加載網頁。加載網頁可能需要多個線程,每個圖像都是在一個單獨線程中被加載的。當用戶按下瀏覽器的停止按鈕時,所有加載網頁的線程被撤銷。
需要撤銷的線程,稱爲目標線程(target thread)。目標線程的撤銷有兩種情況:
異步撤銷:一個線程立即終止目標線程
延遲撤銷:目標線程不斷檢查它是否應終止,這允許目標線程有機會有序終止自己。
線程本地存儲
同一進程的線程共用進程的內存與數據,但線程也會有自己獨有的數據,稱這種數據爲線程本地存儲(Thread-Local Storage, TLS)
調度程序激活
內核與線程庫間可能需要通信,如多對多模型與雙層模型。這種協調允許動態調整內核線程的數量,以便確保最優性能。
輕量級進程(LightWeight Process, LWP):許多系統在實現多對多或雙層模型時,在用戶和內核線程之間增加一箇中間數據結構。這種數據結構就是輕量級進程。
對於用戶級線程庫,LWP表現爲虛擬處理器,以便應用程序調度並運行用戶線程。
每個LWP與一個內核線程相連,而只有內核線程才能通過操作系統調度以便運行於物理處理器。如果內核線程阻塞,LWP也會阻塞,上面的用戶線程也會阻塞。
爲了運行高效,應用程序可能需要一定數量的LWP。通常,每個併發的,阻塞的系統調用需要一個LWP。
假設一個應用程序爲CPU密集型,並且運行在單個處理器上,在這種情況下,同一時間只有一個線程可以運行,所以只需要一個LWP就夠了。
但是,一個IO密集型的應用程序可能需要多個LWP來執行。假設有5個不同的文件讀請求可能同時發生,就需要5個LWP。因爲每個都需要等待內核I/O的完成。如果只有4個LWP,那麼第五個請求就必須等待一個LWP從內核中返回。
調度器激活(scheduler activation):用戶線程庫與內核之間的一種通信方案。
工作流程如下:
- 內核提供一組虛擬處理器(LWP)給應用程序,而應用程序可以調度用戶線程到任何一個可用虛擬處理器。
- 此外,內核應將有關特定事件通知應用程序。
這個步驟叫做回調(upcall),它有線程庫通過**回調處理程序(upcall handler)**來處理。
- 當一個應用程序的線程要阻塞時,內核嚮應用程序發出一個回調,通知它有一個線程將會阻塞並且標識特定線程。
- 內核分配一個新的虛擬處理器給應用程序,應用程序在這個新的虛擬處理器上運行回調處理程序,保存阻塞線程的狀態,並釋放阻塞線程運行的虛擬處理器。
- 回調處理程序調度另一個適合在新的虛擬處理器上運行的線程
- 當阻塞線程等待的事件發生時,內核向線程庫發出另一個回調,通知它先前阻塞的線程現在有資格運行了。
該事件的回調處理程序也需要一個虛擬處理器,內核可能分配一個新的虛擬處理器,或搶佔一個用戶線程並在其虛擬處理器上運行回調處理程序。
- 在阻塞線程有資格運行後,應用程序在可用虛擬處理器上運行符合條件的線程。
以上過程展示了線程庫內的線程怎麼與內核進行通信並進行調度。
總結與梳理
多線程編程的兩種並行類型
多線程四種模型與特點
線程庫的實現
線程池的作用與優點
多線程之間的信號處理
線程撤銷的概念
調度程序激活的流程