關(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ōu)先級(jí)的線程的優(yōu)先級(jí)隨著其最近處理器使用的增加而變低,反之亦然。這暗示一般來(lái)講,某線程最近被分配的時(shí)間片越多,則它被分配下一個(gè)時(shí)間片的可能性越小。
- 不固定優(yōu)先級(jí)的線程的優(yōu)先級(jí)隨著其 nice 值的增加而變低,反之亦然。
注: 使用多處理器運(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ì)列中。
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ī)則。
在linux下我們可以通過(guò)
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
來(lái)創(chuàng)建線程,但是如何設(shè)置線程的優(yōu)先級(jí)呢?
在討論這個(gè)問(wèn)題的時(shí)候,我們先要確定當(dāng)前線程使用的調(diào)度策略,posix提供了
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);函數(shù)來(lái)獲取所
使用的調(diào)度策略,它們是:
SCHED_FIFO, SCHED_RR 和 SCHED_OTHER。
我們可以使用
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
來(lái)獲取線程線程可是設(shè)置的最大和最小的優(yōu)先級(jí)值,如果調(diào)用成功就返回最大和最小的優(yōu)先級(jí)值,否則返回-1。
從我現(xiàn)在運(yùn)行的linux系統(tǒng)中,我使用下列程序獲取了對(duì)應(yīng)三種調(diào)度策略中的最大和最小優(yōu)先級(jí):
policy = SCHED_OTHER
Show current configuration of priority
max_priority = 0
min_priority = 0
Show SCHED_FIFO of priority
max_priority = 99
min_priority = 1
Show SCHED_RR of priority
max_priority = 99
min_priority = 1
Show priority of current thread
priority = 0
Set thread policy
Set SCHED_FIFO policy
policy = SCHED_FIFO
Set SCHED_RR policy
policy = SCHED_RR
Restore current policy
policy = SCHED_OTHER
我們可以看到
SCHED_OTHER
是不支持優(yōu)先級(jí)使用的,而SCHED_FIFO和SCHED_RR支持優(yōu)先級(jí)的使用,他們分別為1和99,
數(shù)值越大
優(yōu)先級(jí)越高。 從上面的結(jié)果我們可以看出,如果程序控制線程的優(yōu)先級(jí),一般是用
pthread_attr_getschedpolicy來(lái)獲取系統(tǒng)使用的調(diào)度策略,如果是SCHED_OTHER的話(huà),表明當(dāng)前策略
不支持線程優(yōu)先級(jí)的使用,否則可以。當(dāng)然所設(shè)定的優(yōu)先級(jí)范圍必須在最大和最小值之間。我們可以通過(guò)
sched_get_priority_max
和sched_get_priority_min來(lái)獲取。
可能網(wǎng)友會(huì)問(wèn),是否我們可以通過(guò)
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);來(lái)設(shè)定自己所需的
調(diào)度策略呢?我覺(jué)得是完全可以的(有些系統(tǒng)需要定義
_POSIX_THREAD_PRIORITY_SCHEDULING),只要
系統(tǒng)實(shí)現(xiàn)了對(duì)應(yīng)的調(diào)用策略。
說(shuō)了半天,我們還沒(méi)有說(shuō),在系統(tǒng)允許使用線程優(yōu)先級(jí)別的時(shí)候,如何設(shè)置優(yōu)先級(jí)別呢?
int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr,
struct sched_param *param);
上面兩個(gè)函數(shù)分別用于設(shè)置線程的優(yōu)先級(jí),struct sched_param的定義如下
struct sched_param
{
int __sched_priority; //所要設(shè)定的線程優(yōu)先級(jí)
};
使用的測(cè)試程序:
#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ó)源碼