線程的基本概念和調(diào)度策略 (2007-04-28 11:21)
分類: linux學(xué)習(xí)
關(guān)鍵詞:線程 進程 優(yōu)先級 調(diào)度策略 時間片
一、線程的基本概念 進程(process)和文件(files)是UNIX/Linux操作系統(tǒng)兩個最基本的抽象。進程是處于執(zhí)行期的程序和它所包含的資源的總和,也就是說一個進程就是處于執(zhí)行期的程序。一個線程(thread)就是運行在一個進程上下文中的一個邏輯流,不難看出,線程是進程中最基本的活動對象。
在傳統(tǒng)的系統(tǒng)中,一個進程只包含一個線程。但在現(xiàn)代操作系統(tǒng)中,允許一個進程里面可以同時運行多個線程,這類程序就被稱為多線程程序。所有的程序都有一個主線程(main thread),主線程是進程的控制流或執(zhí)行線程,見圖1。在多線程程序中,主線程可以創(chuàng)建一個或多個對等線程(peer thread),從這個時間點開始,這些線程就開始并發(fā)執(zhí)行,見圖2。主線程和對等線程的區(qū)別僅在于主線程總是進程中第一個運行的線程。從某種程度上看,線程可以看作是輕量級的進程(lightweight process)。在Linux操作系統(tǒng)中,內(nèi)核調(diào)度的基本對象是線程,而不是進程,所以進程中的多個線程將由內(nèi)核自動調(diào)度。
每個線程都擁有獨立的線程上下文(thread context),線程ID(Thread ID,TID),程序計數(shù)器(pc),線程棧(stack),一組寄存器(register)和條件碼。其中,內(nèi)核正是通過線程ID(TID)來識別線程,進行線程調(diào)度的。
圖 1多線程進程的控制流
圖 2并發(fā)線程執(zhí)行模型
線程和進程在很多方面是相似的。相同點主要表現(xiàn)在如下幾方面: 1) 比如都具有ID,一組寄存器,狀態(tài),優(yōu)先級以及所要遵循的調(diào)度策略。
2) 每個進程都有一個進程控制塊,線程也擁有一個線程控制塊(在Linux內(nèi)核,線程控制塊與進程控制塊用同一個結(jié)構(gòu)體描述,即struct task_struct),這個控制塊包含線程的一些屬性信息,操作系統(tǒng)使用這些屬性信息來描述線程。
3) 線程和子進程共享父進程中的資源。
4) 線程和子進程獨立于它們的父進程,競爭使用處理器資源。
5) 線程和子進程的創(chuàng)建者可以在線程和子進程上實行某些控制,比如,創(chuàng)建者可以取消、掛起、繼續(xù)和修改線程和子進程的優(yōu)先級。
6) 線程和子進程可以改變其屬性并創(chuàng)建新的資源
除了這些相同點,在很多方面也存在著差異:
1) 主要區(qū)別:每個進程都擁有自己的地址空間,但線程沒有自己獨立的地址空間,而是運行在一個進程里的所有線程共享該進程的整個虛擬地址空間
2) 線程的上下文切換時間開銷比進程上下文切換時間開銷要小的多
3) 線程的創(chuàng)建開銷遠(yuǎn)遠(yuǎn)小于進程的創(chuàng)建
4) 子進程擁有自己的地址空間和數(shù)據(jù)段的拷貝,因此當(dāng)子進程修改它的變量和數(shù)據(jù)時,它不會影響父進程中的數(shù)據(jù),但線程可以直接訪問它進程中的數(shù)據(jù)段
5) 進程之間通訊必須使用進程間通訊機制,但線程可以與進程中的其他線程直接通訊
6) 線程可以對同一進程中的其他線程實施大量控制,但進程只能對子進程實施控制
7) 改變主線程的屬性可能影響進程中其他的線程,但對父進程的修改不影響子進程。
二、進程和線程的優(yōu)先級
進程優(yōu)先級只是線程優(yōu)先級的前身。當(dāng)調(diào)用 fork() 子例程時,會創(chuàng)建一個進程和一個要在其中運行的線程。線程的優(yōu)先級歸結(jié)于進程。
內(nèi)核為每個線程維護一個優(yōu)先級值(有時稱為調(diào)度優(yōu)先級)。優(yōu)先級值是一個正整數(shù)且與關(guān)聯(lián)線程的重要性的變化方向相反。也就是說,較小的優(yōu)先級值表示一個相對重要的線程。當(dāng)調(diào)度程序?qū)ふ揖€程進行分派時,它選擇具有較小優(yōu)先級值的可分派線程。 線程可以有固定的優(yōu)先級或不固定的優(yōu)先級。優(yōu)先級固定的線程的優(yōu)先級值是一個常量,而優(yōu)先級不固定的線程的優(yōu)先級值根據(jù)用戶線程最小優(yōu)先級級別(常量 40)、線程的 nice 值(缺省值是 20,可隨意由 nice 或 renice 命令進行設(shè)置)和其處理器使用的損失而變化。 線程的優(yōu)先級可以固定成某個值,如果用 setpri() 子例程設(shè)置(固定)它們的優(yōu)先級的話,它們可以具有小于 40 的優(yōu)先級值。這些線程不會受到調(diào)度程序重算算法的影響。如果它們的優(yōu)先級值固定且小于 40,這些線程將在可以運行所有用戶線程之前運行和完成。例如,一個具有固定值 10 的線程將在具有固定值 15 的線程之前運行。 用戶可以應(yīng)用 nice 命令使線程的不固定優(yōu)先級變低。系統(tǒng)管理員可將一個負(fù)的 nice 值應(yīng)用給線程,這樣就給了它較好的優(yōu)先級。 下圖顯示了一些可以更改優(yōu)先級值的方法。 圖 1. 如何確定優(yōu)先級值. 插圖顯示了如何能在執(zhí)行過程中或應(yīng)用了 nice 命令之后更改線程調(diào)度優(yōu)先級值。優(yōu)先級值越小,線程優(yōu)先級越高。開始時,nice 值缺省為 20 而基本優(yōu)先級缺省為 40。執(zhí)行中發(fā)生了處理器損失后,nice 的值仍然保持 20,基本優(yōu)先級仍然保持 40。在運行 renice —5 命令后及使用和以前相同的處理器(CPU)的情況下,nice 值現(xiàn)在是 15 而基本優(yōu)先級仍然是 40。在以 50 的值發(fā)出子例程 setpri() 之后,固定優(yōu)先級現(xiàn)在是 50 而 nice 值和處理器(CPU)的使用不相關(guān)。
![]() 線程的 nice 值在創(chuàng)建線程時設(shè)置并且在線程的整個生命期中都是常量,除非用戶通過 renice 命令或 setpri()、setpriority()、thread_setsched() 或 nice() 系統(tǒng)調(diào)用明確更改了它的值。 處理器損失是一個整數(shù),它通過線程最近的處理器使用來計算。如果每次在一個 10 ms 的時鐘滴答結(jié)束時線程受處理器控制,則最近的處理器使用值近似加 1,直到達到最大值 120。每個滴答的實際優(yōu)先級損失隨著 nice 的值增加。所有線程的最近處理器使用值每秒重算一次。 結(jié)果如下:
注: 使用多處理器運行隊列及其負(fù)載平衡機制以后,nice 或 renice 的值對線程的優(yōu)先級可能沒有預(yù)期的影響,因為較低優(yōu)先級的運行時間可能等于或大于較高優(yōu)先級的運行時間。要求 nice 或 renice 產(chǎn)生預(yù)期效果的線程應(yīng)該放在全局運行隊列中。 三、線程調(diào)度策略 Pthread 調(diào)度 POSIX 定義一種優(yōu)先級調(diào)度模型,此模型確定任何兩個線程相對于對方的重要程度。 每當(dāng)有一個以上的線程可以運行—執(zhí)行就緒—時,系統(tǒng)都將選擇具有最高優(yōu)先級的線程。 POSIX 線程調(diào)度語義是按照一種概念模型定義的,在此概念模型中有一個有效優(yōu)先級范圍,并且有一組線程列表,每個優(yōu)先級分配有一個列表。根據(jù)線程的調(diào)度優(yōu)先級,將任何可運行的線程放置在其中一個線程列表上。線程列表內(nèi)的排序取決于調(diào)度策略。因此,每個線程都受其調(diào)度優(yōu)先級及其調(diào)度策略控制。 調(diào)度策略的作用是定義這些線程列表上的操作,如在列表內(nèi)和列表之間移動線程。 不管策略如何,POSIX 都指定具有最高優(yōu)先級的線程列表中的第一個線程應(yīng)為當(dāng)前運行的線程。 調(diào)度線程優(yōu)先級的能力是 POSIX 標(biāo)準(zhǔn)中的一個選項,由符號 POSIX_THREAD_PRIORITY_SCHEDULING 指定。支持此選項的 POSIX 實現(xiàn)還必須提供給線程指定實時調(diào)度策略和優(yōu)先級的機制。 強制性策略為 SCHED_FIFO、SCHED_RR 和 SCHED_OTHER。 SCHED_FIFO(先進先出)策略按線程在執(zhí)行前在列表上存在的時間對列表上的線程進行排序。處于列表首位的線程通常為在列表上存在時間最長的線程,而處于末尾的線程在列表上存在的時間最短。此策略允許一個線程一直運行,直到具有較高優(yōu)先級的另一個線程已準(zhǔn)備好運行,或者直到當(dāng)前線程自動阻止。如果此線程被占據(jù),它就繼續(xù)處于其線程優(yōu)先級列表的首位;如果此線程阻止,當(dāng)它再次成為一個可運行的線程時,將被添加到此線程所在的優(yōu)先級列表的末尾。 SCHED_RR (循環(huán)法)策略與 SCHED_FIFO 策略相同,不同的只是運行的線程在被占據(jù)之前只能運行有限的時間長度。當(dāng)超過此固定時限時,運行的線程就被放到線程優(yōu)先級列表的末尾,而現(xiàn)在處于列表首位的線程將成為運行的線程。 此策略的作用是確保具有相同優(yōu)先級的多個 SCHED_RR 線程能共享處理器。 SCHED_OTHER 策略是針對具體實現(xiàn)的,相容的 POSIX 實現(xiàn)必須記錄此策略的行為。 一個實施可將此策略定義為與 SCHED_FIFO 或 SCHED_RR 相同,也可以定義為與這兩種策略完全不同的策略。 POSIX 定義此類策略的目的是為相容的程序提供一種方法來表明這些程序不需要可移植的實時調(diào)度策略。 每種調(diào)度策略都有一個優(yōu)先級的有效范圍;對于 SCHED_FIFO 和 SCHED_RR,此范圍必須至少是 32,而對于 SCHED_OTHER,此范圍是針對具體實現(xiàn)的。 可以從 sched_get_priority_min() 函數(shù)和 sched_get_priority_max() 函數(shù)確定優(yōu)先級的范圍。 PThread 調(diào)度爭用范圍和分配域 除線程調(diào)度策略和線程優(yōu)先級外,還有其他兩種調(diào)度控制: 線程調(diào)度爭用范圍和線程調(diào)度分配域。 爭用范圍定義競爭使用處理資源的線程集。 POSIX 定義兩個爭用范圍:系統(tǒng)中的所有線程(或 PTHREAD_SCOPE_SYSTEM)以及一個進程中的所有線程(或 PTHREAD_SCOPE_PROCESS)。 系統(tǒng)爭用范圍中的一個線程與系統(tǒng)中所有其他線程(包含其他進程中的那些線程)爭用資源。 一個進程中的高優(yōu)先級線程可阻止其他進程中的系統(tǒng)爭用范圍線程運行。 進程爭用范圍內(nèi)的線程在進程內(nèi)進行調(diào)度,這表示只在一個進程內(nèi)的所有線程間進行調(diào)度。 進程爭用范圍通常表示由操作系統(tǒng)選擇要運行的進程,而進程本身包含一個內(nèi)部調(diào)度程序來試圖針對進程內(nèi)的線程實現(xiàn) POSIX 調(diào)度規(guī)則。 四、測試源代碼 在linux下我們可以通過 #include <iostream> #include <pthread.h> #include <sched.h> #include <assert.h> using namespace std; static int get_thread_policy( pthread_attr_t &attr ) { int policy; int rs = pthread_attr_getschedpolicy( &attr, &policy ); assert( rs == 0 ); switch ( policy ) { case SCHED_FIFO: cout << "policy = SCHED_FIFO" << endl; break; case SCHED_RR: cout << "policy = SCHED_RR" << endl; break; case SCHED_OTHER: cout << "policy = SCHED_OTHER" << endl; break; default: cout << "policy = UNKNOWN" << endl; break; } return policy; } static void show_thread_priority( pthread_attr_t &attr, int policy ) { int priority = sched_get_priority_max( policy ); assert( priority != -1 ); cout << "max_priority = " << priority << endl; priority = sched_get_priority_min( policy ); assert( priority != -1 ); cout << "min_priority = " << priority << endl; } static int get_thread_priority( pthread_attr_t &attr ) { struct sched_param param; int rs = pthread_attr_getschedparam( &attr, ¶m ); assert( rs == 0 ); cout << "priority = " << param.__sched_priority << endl; return param.__sched_priority; } static void set_thread_policy( pthread_attr_t &attr, int policy ) { int rs = pthread_attr_setschedpolicy( &attr, policy ); assert( rs == 0 ); get_thread_policy( attr ); } int main( void ) { pthread_attr_t attr; struct sched_param sched; int rs; rs = pthread_attr_init( &attr ); assert( rs == 0 ); int policy = get_thread_policy( attr ); cout << "Show current configuration of priority" << endl; show_thread_priority( attr, policy ); cout << "Show SCHED_FIFO of priority" << endl; show_thread_priority( attr, SCHED_FIFO ); cout << "Show SCHED_RR of priority" << endl; show_thread_priority( attr, SCHED_RR ); cout << "Show priority of current thread" << endl; int priority = get_thread_priority( attr ); cout << "Set thread policy" << endl; cout << "Set SCHED_FIFO policy" << endl; set_thread_policy( attr, SCHED_FIFO ); cout << "Set SCHED_RR policy" << endl; set_thread_policy( attr, SCHED_RR ); cout << "Restore current policy" << endl; set_thread_policy( attr, policy ); rs = pthread_attr_destroy( &attr ); assert( rs == 0 ); return 0; } 作者注:以上內(nèi)容純屬拼湊而成,如果你沒看明白,清直接看源地址。 直接引用文獻(不一定是作者出處): 1、希望之光的博客 2、ITPUB論壇 3、IBM AIX文檔 4、中國源碼 |
|