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