線程是一種輕量級進程。與進程相比,線程給操作系統帶來的創建、維護和管理負擔要輕,因為與線程相關的信息非常少。它同時意味著線程的代價或開銷比進程少。
線程可以在它的進程中創建另一個線程。一個進程中的所有線程稱做同位體(peer)。所有的線程共享進程的資源和內存。線程不擁有任何資源。由任何線程創建的任何資源都被它的同位體共享。線程也可以在進程中掛起、恢復和終止其他線程。
進程與它的線程幾乎共享一切,包括它的資源和環境變量。數據、文本片斷以及所有資源都與進程相關,而不是與線程相關。線程是進程的租借者。線程發揮作用所需的一切都由進程提供和定義。所有的線程都有一個線程ID、定義線程狀態的寄存器組、優先權以及堆棧。
1. 多線程處理
多線程進程擁有的線程多于一個,每個線程都執行進程的程序代碼。如果進程只有一個線程,則稱做主線程,它只有一個控制程序。進程的指令可以分解成子任務(典型情況是函數),這些子任務可以異步執行。線程可以執行一個函數或一組函數,每個線程的程序計數器將跟蹤執行它的下一條指令,因此每個線程允許進程的函數獨立執行,而與進程的主控制流程無關。
多線程應用程序指那些可以受益于子任務異步執行的應用程序。
一個有趣的NLP應用實例:
a. 驗證字符串;b. 從字符串中提取噪音; c. 符號化字符串; d. 獲取命令、文件名以及設備符號; e. 構建MCI命令并執行。
命令解析器由以下子任務組成:
a. 驗證字符串; b.提取噪音; c. 符號化字符串; d. 執行。
符號化字符串子任務調用:
a. 獲得命令符號; b. 獲得設備名字符號; c. 獲得文件名符號。
2. 線程與進程的相似之處
線程和進程都有ID、寄存器組、狀態以及優先權。它們與之關聯的信息塊分別稱做線程塊(thread block)和進程信息塊(process information block)。線程和子進程共享父進程的資源。進程打開的資源,線程和父進程的子進程可以立即訪問,不需要額外的初始化或準備。創建后,線程和進程是與父或創建者獨立的實體。父和子進程、線程一起競爭使用處理器。創建者對它創建的進程或線程可施加一些控制,同時也可以銷毀、掛起、恢復和更改進程或線程的優先權。線程和進程可以改變自己的屬性和創建新的資源,但它們不能訪問其它進程或其之上線程的資源。
3. 線程與進程的不同之處
線程沒有自己的地址空間。同一個進程內的多個線程在相同的地址空間內,可以容易地共享資源,它們可通過讀取和寫入數據到進程變量來通信。但父子進程則有著各自獨立的地址空間,若要共享內存必須創建一個共享內存區域。父子進程必須使用進程間通信(IPC)機制在兩者間通信和傳遞數據。
對其他進程的控制限制于父子進程之間進行,子進程無法對父進程施加控制。但進程內的線程則被視作同位體,處于相等的級別。不管是哪一個線程創建了哪一個線程,進程內的任何線程都可以銷毀、掛起、恢復或更改其它線程的優先權。線程也要對整個進程施加控制。進程內的任何線程可以通過銷毀主線程來銷毀該進程。另外銷毀主線程將導致該進程的所有線程銷毀。對主線程的修改可能影響進程的所有線程。對進程優先權的更改將改變進程內繼承了優先權但沒有作修改的所有線程的優先權。
4. 線程的優點
多線程相對并發多進程而言需要較小的上下文切換開銷。它還可以增加應用程序的吞吐能力。在單線程程序中一個I/O請求就可以停止整個進程,但通過多線程,當一個線程等待I/O請求時,應用程序可以繼續執行。即一個線程阻塞等待I/O完成,其它的線程可以繼續執行。線程并不需要子任務間的特殊通信機制,可以輕易地在任務間傳遞與接收數據,節省了系統資源的使用。如果使用多進程,則程序在啟動、維持特殊通信機制時必須使用這些系統資源。線程通過使用進程內所有線程共享的內存來通信。
5. 線程的缺點
線程需要同步并發訪問內存。進程是孤立的。如果任務分解為不同的進程,如果一個進程創建了不良數據,這一數據只限制于該進程。如果任務分解成多進程,一個線程可能產生影響其他線程的不良數據。相對于線程而言,進程更加獨立。一個應用程序可以將任務分成多個進程。這些進程打包成模塊,這些模塊可以用于其它應用程序中。其他應用程序中的模塊可以導入新的應用程序。線程依賴于它所屬的進程,并且不能退出到創建它的進程之外。
6. 線程類型
關于如何使用線程來完成它們執行的子任務有如下三個策略:
a. 休眠(sleeper)和單步(one-shot); b. 先占工作(anticipating work); c. 延遲工作(deferring work)。
6.1 休眠和單步
休眠線程在系統中某事件發生前一直掛起。當事件發生時,得到恢復,并執行任務。此后,線程為非激活,直到下次事件再次發生。單步線程實踐上就是休眠線程,但它們只執行任務一次后就終止。休眠與單步線程又稱為監視線程,它們監視應用程序的某些方面,等待事件的發生,然后作為反應采取一些相應的行為。
6.2 先占工作
某個任務被請求前執行時就使用先占工作策略。對情況進行估計,并先占執行一些動作。它促進使用永不等待規則(never-wait rule)。它的目的是阻止非激活。這一規則表明預先執行一個可能不被使用的任務比處于空閑要好。
6.3 延遲工作
線程可以將任務延遲或推遲到將來某個時間執行的另一個線程。進行延遲的線程可能正執行一些重要的任務,不能執行另一項任務。此時線程可以創建另一個線程來執行任務,或者將任務交給一些已經存在的線程。可以在后臺把一項延遲任務交給分離線程執行。
7. 線程相關信息
Win32環境中,線程具有一個模擬線程環境的信息結構,這個結構喚醒標志、線程的消息隊列、虛擬化輸入隊列及其他變量。
POSIX環境有一個針對線程的屬性對象。它可與一個線程關聯,也可與多個線程關聯。這些線程屬性包括堆棧大小、優先權、規劃策略、作用域、繼承以及分離狀態。也可能包含處理器信息。在多處理器系統中,可以將線程分配給特定的處理器或處理器范圍。在對稱或非對稱多處理器系統中,系統提供一個返回特定線程處理器親合掩碼的函數。它是一個位數組,其中的每個位表示線程可以運行的處理器。
8. 線程創建
進程的主線程由操作系統自動創建。通過調用線程創建函數來創建后續或第二線程。我們可以通過調用線程庫提供的函數也可直接調用OS提供的函數來創建線程。由線程庫提供的函數在進行一些內務處理后最終調用OS函數。
線程在執行完畢后終止。當線程返回到創建它的函數時自動終止。線程的資源被釋放。線程也可以顯示調用一個線程終止函數。任何等待該線程終止的線程都將收到終止線程的ID或其信號。如果終止線程為主線程,它將導致整個進程的終止。
分離進程為異步子進程,它不繼承父進程的任何屬性,用作后臺進程,不需要鍵盤輸入或屏幕輸出。分離進程在終止時不返回父進程,分離線程在這一點與分離進程相似。當線程終止時,終止線程的ID和狀態由OS保存。如果進程不需要知道線程何時終止,就可以創建為分離線程。當線程終止時,線程ID和完成狀態不會保存,也不會釋放由線程使用的任何資源。為了創建一個分離線程,在創建的同時設置分離標志。運行的線程也可以為分離的。
守護程序(daemon)是分離進程的一個例子。守護進程在部分時間為阻塞狀態,直到OS中發生了某事件,它才變成激活
Win32環境中,一個進程中的線程可以創建另一個進程中的線程。這稱做遠程線程。創建遠程線程的過程與在同一進程中創建線程相似,除了需要一個目標進程的句柄之外。線程在目標進程的地址空間中執行。
9. 線程堆棧
每個線程都有自己的堆棧,它的大小在創建線程時被固定。線程堆棧在進程的地址空間的堆棧片斷中分配。如果線程的創建都沒有指定線程堆棧的大小,則由系統分配為缺省大小。它的大小與OS有關,取決于線程的最大數量、進程地址空間的分配大小以及OS資源所使用的空間。
10. 線程控制
進程內的線程被視為同位體,對資源有同樣的訪問權限、相互間施加同等的控制。它們對父線程的資源、環境變量以及看作全局的數據有相同的訪問權限。線程能夠訪問其他線程打開或創建的資源。也可以銷毀或創建其他線程包括主線程。
線程可以掛起進程內另一個線程的執行。掛起線程直到調用一個恢復它的函數時才會執行。進程內的任何線程都可以恢復掛起線程,除了掛起線程自己。線程可以在指定時間段內掛起自身。
11. 線程優先權
優先權規劃用于決定哪一個線程使用處理器以及使用多長時間。在POSIX環境中,同一優先級的線程可以使用FIFO或循環規劃或其他策略來規劃。
對于其它線程所依賴的線程,應當更改它的優先權加速它的運行。線程優先權不應該為了讓特定線程得到更多的處理器時間而更改。這將影響系統的整體操作性能。較高優先權的線程將主宰處理器,剝奪了其他線程有用的處理器時間,這就稱做線程的饑餓。使用動態優先權機制的系統將對這種情況和系統中的其他 變化做出反應。
線程的優先權(priority)包括一個優先類(priority class)和一個優先級(priority level)。線程的優先類由它所屬進程的優先類決定。新線程的優先級從創建它的線程繼承。每個優先類都有一個級范圍。
后臺進程的優先權應當設的比前臺進程低些。這樣后臺線程在沒有前臺線程準備執行時才使用處理器。所以當后臺進程變成前臺進程時,該進程的所有線程都將被推進(boost)或提升(raise)。這允許前臺線程有能力對用戶迅速作出反應。、
12. 線程狀態
線程是處理器上的可規劃單元(schedulable unit)。進程的每個線程都獨立執行,同時競爭處理器時間。它們與同一進程中以及其他進程中的線程進行競爭。每個線程都有自己的上下 文和線 程狀態。
為了阻塞進程,進程之上的所有線程都必須處于阻塞狀態。
13. 線程與資源
在進程內,對于內存、處理器以及其他全局分配資源,線程都要與進程的其他 線程以及其他進程的線程來競爭。一些OS允許用戶設置單個線程的競爭域。
14. 線程的實現模型:用戶級線程
有三種線程實現(用戶級線程;核心級線程;以上兩種實現的混合途徑)。
通過用戶線實現,使用線程的程序鏈接到線程庫。每個庫函數調用都被翻譯或映射屬于進程一部分的運行時系統能夠識別的調用。運行時系統實現線程管理。線程由庫進行管理。這類線程包在OS的上層運行。對于OS這些線程不可見。這一類可執行可規劃單元為-進程-線程(one thread per process)。不管庫管理著多少線程,實際分配 處理器時間的線程只有一個,即主線程。用戶線程具有多對一的線程關系。
核心級線程由OS來管理。進程有多個線程,對于核心都是可見的。通過核心線程,每創建一個線程,都存在一個創建核心線程的系統調用。這種一對一的關系使得它的開銷比用戶級線程大。不過用戶級線程的一個缺點是無法使用多個處理器來使用系統。純種管理只能發生于分配給該進程的單處理器上(想到了Python程序中多線程的實現正是如此)。通過kernel thread,則只要資源足夠,可以給它自己的處理器分配線程。
結合user thread與kernel thread的優點可得到混合線程方式。在此模式中,線程被看作一個用戶級線程,但被映射到核心級實體。只需創建少量核心線程。可能許多線程被請求,但只能創建當前激活的線程。創建一個線程池(thread pool)從一個線程到另一個線程進行模擬切換。線程池實際決定需要多少核心實體。POSIX.1c使用混合線程實現。