线程的基本概念和调度策略

关 键词:线程 进程 优先级 调度策略 时间片
一、 线程的基本概念
进程(process )和文件(files ) 是UNIX/Linux操作系统两个最基本的抽象。进程是处于执行期的程序和它所包含的资源的总和,也就是说一个进程就是处于执行期的程序。一个线 程thread )就是运行在一个进程上下文中的一个逻辑流,不难看出,线程是进程中最基本的活动对象。
在传统的系统中,一个进程只包含一个线程。但在现代操作系统中,允许一个进程里面 可以同时运行多个线程,这类程序就被称为多线程程序。所有的程序都有一个主线程(main thread ),主线程是进程的控制流或执 行线程,见图1。在多线程程序中,主线程可以创建一个或多个对等线程(peer thread ),从这个时间点开始,这些线程就开始并 发执行,见图2。主线程和对等线程的区别仅在于主线程总是进程中第一个运行的线程。从某种程度上看,线程可以看作是轻量级的进程(lightweight process )。在Linux操作系统中,内核调度的基本对象是线程,而不是进程,所以进程中的多个线程将由内核自动调度。
每个线程都拥有独立的线程上下文(thread context ), 线程ID(Thread ID ,TID ),程序计数器(pc ),线程栈(stack ), 一组寄存器(register )和条件码。其中,内核正是通过线程ID(TID)来识别线程,进行线程调度的。
图 1 多 线程进程的控制流
图 2 并 发线程执行模型

线程和进 程在很多方面是相似的。相同点主要表现在如下几方面:
1)        比如都具有ID,一组寄存器,状态,优先级以及所要遵循的 调度策略。
2)        每个进程都有一个进程控制块,线程也拥有一个线程控制块 (在Linux内核,线程控制块与进程控制块用同一个结构体描述,即struct task_struct),这个控制块包含线程的一些属性信息,操作系统使用这些属性信息来描述线程。
3)        线程和子进程共享父进程中的资源。
4)        线程和子进程独立于它们的父进程,竞争使用处理器资源。
5)        线程和子进程的创建者可以在线程和子进程上实行某些控制, 比如,创建者可以取消、挂起、继续和修改线程和子进程的优先级。
6)        线程和子进程可以改变其属性并创建新的资源
除了这些相同点,在很多方面也存在着差异:
1)        主要区别:每个进程都拥有自己的地址空间,但线程没有自己 独立的地址空间,而是运行在一个进程里的所有线程共享该进程的整个虚拟地址空间
2)        线程的上下文切换时间开销比进程上下文切换时间开销要小的 多
3)        线程的创建开销远远小于进程的创建
4)        子进程拥有自己的地址空间和数据段的拷贝,因此当子进程修 改它的变量和数据时,它不会影响父进程中的数据,但线程可以直接访问它进程中的数据段
5)        进程之间通讯必须使用进程间通讯机制,但线程可以与进程中 的其他线程直接通讯
6)        线程可以对同一进程中的其他线程实施大量控制,但进程只能 对子进程实施控制
7)        改变主线程的属性可能影响进程中其他的线程,但对父进程的修改不影响子进 程。
  二、进程和线程的优先级
进程优先级只是线程优先级的前 身。当调用 fork() 子例程时,会创建一个进程和一个要在其中运行的线程。线程的优先级归结于进程。

内核为每个线程维护一个优先级值 (有时称为调度优先级 )。优先级值是一个正整 数且与关联线程的重要性的变化方向相反。也就是说,较小的优先级值表示一个相对重要的线程。当调度程序寻找线程进行分派时,它选择具有较小优先级值的可分 派线程。

线程可以有固定的优先级或不固定的优先级。优先级固定的线程的优先级值是一个常量,而优先级不固定的线程的优先级值 根据用户线程最小优先级级别(常量 40)、线程的 nice 值(缺省值是 20,可随意由 nicerenice 命令进行设置)和其处理器使用的损失而变化。

线程的优先级可以固定成某个值,如果用 setpri() 子例程设置(固定)它们的优先级的话,它们可以具有小于 40 的优先级值。这些线程不会受到调度程序重算算法的影响。如果它们的优先级值固定且小于 40,这些线程将在可以运行所有用户线程之前运行和完成。例如,一个具有固定值 10 的线程将在具有固定值 15 的线程之前运行。

用户可以应用 nice 命令使线程的不固定优先级变低。系统管理员可将一个负的 nice 值应用给线程,这样就给了它较好的优先级。

下图显示了一些可以更改优先级值的方法。

图 1. 如何确定优先级值 . 插图显示了如何能在执行过程中或应用了 nice 命令之后更改线程调度优先级值。优先级值越小,线程优先级越高。开始时,nice 值缺省为 20 而基本优先级缺省为 40。执行中发生了处理器损失后,nice 的值仍然保持 20,基本优先级仍然保持 40。在运行 renice —5 命令后及使用和以前相同的处理器(CPU)的情况下,nice 值现在是 15 而基本优先级仍然是 40。在以 50 的值发出子例程 setpri() 之后,固定优先级现在是 50 而 nice 值和处理器(CPU)的使用不相关。 h02i1 
图形

线程的 nice 值在创建线程时设置并且在线程的整个生命期中都是常量,除非用户通过 renice 命令或 setpri()setpriority()thread_setsched()nice() 系统调用明确更改了它的值。

处理器损失是一个整数,它通过线程最近的处理器使用来计算。如果每次在一个 10 ms 的时钟滴答结束时线程受处理器控制,则最近的处理器使用值近似加 1,直到达到最大值 120。每个滴答的实际优先级损失随着 nice 的值增加。所有线程的最近处理器使用值每秒重算一次。

结果如下:

  •        不固定优先级的线程的优先级随着其最近处理器使用的增加而变低,反之亦然。这暗示一般来讲,某线程最近被分配的时间片越多,则它被分配下一个时间片的可能 性越小。
  •        不固定优先级的线程的优先级随着其 nice 值的增加而变低,反之亦然。
注: 使用多处理器运行队列及其负载平衡机制以后,nicerenice 的值对线程的优先级可能没有预期的影响,因为较低优先级的运行时间可能等于或大于较高优先级的运行时间。要求 nicerenice 产生预期效果的线程应该放在全局运行队列中。
  三、 线程调度策略
Pthread 调度
POSIX 定义一种优先级调度模型,此模型确定任何两个线程相对于对方的重要程度。 每当有一个以上的线程可以运行—执行就绪—时,系统都将选择具有最高优先级的线程。

POSIX 线程调度语义是按照一种概念模型定义的,在此概念模型中有一个有效优先级范围,并且有一组线程列表,每个优先级分配有一个列表。根据线程的调度优先级,将 任何可运行的线程放置在其中一个线程列表上。线程列表内的排序取决于调度策略。因此,每个线程都受其调度优先级及其调度策略控制。

调度策 略的作用是定义这些线程列表上的操作,如在列表内和列表之间移动线程。 不管策略如何,POSIX 都指定具有最高优先级的线程列表中的第一个线程应为当前运行的线程。

调度线程优先级的能力是 POSIX 标准中的一个选项,由符号 POSIX_THREAD_PRIORITY_SCHEDULING 指定。支持此选项的 POSIX 实现还必须提供给线程指定实时调度策略和优先级的机制。 强制性策略为 SCHED_FIFO、SCHED_RR 和 SCHED_OTHER。

SCHED_FIFO(先 进先出)策略按线程在执行前在列表上存在的时间对列表上的线程进行排序。处于列表首位的线程通常为在列表上存在时间最长的线程,而处于末尾的线程在列表上 存在的时间最短。此策略允许一个线程一直运行,直到具有较高优先级的另一个线程已准备好运行,或者直到当前线程自动阻止。如果此线程被占据,它就继续处于 其线程优先级列表的首位;如果此线程阻止,当它再次成为一个可运行的线程时,将被添加到此线程所在的优先级列表的末尾。

SCHED_RR (循环法)策略与 SCHED_FIFO 策略相同,不同的只是运行的线程在被占据之前只能运行有限的时间长度。当超过此固定时限时,运行的线程就被放到线程优先级列表的末尾,而现在处于列表首位 的线程将成为运行的线程。 此策略的作用是确保具有相同优先级的多个 SCHED_RR 线程能共享处理器。

SCHED_OTHER 策略是针对具体实现的,相容的 POSIX 实现必须记录此策略的行为。 一个实施可将此策略定义为与 SCHED_FIFO 或 SCHED_RR 相同,也可以定义为与这两种策略完全不同的策略。 POSIX 定义此类策略的目的是为相容的程序提供一种方法来表明这些程序不需要可移植的实时调度策略。

每种调度策略都有一个优先级的有效范围;对于 SCHED_FIFO 和 SCHED_RR,此范围必须至少是 32,而对于 SCHED_OTHER,此范围是针对具体实现的。 可以从 sched_get_priority_min() 函数和 sched_get_priority_max() 函数确定优先级的范围。

PThread 调度争用范围和分配域
除线程调度策略和线程优先级外,还有其他两种调度控制: 线程调度争用范围和线程调度分配域。

争用范围定 义竞争使用处理资源的线程集。 POSIX 定义两个争用范围:系统中的所有线程(或 PTHREAD_SCOPE_SYSTEM)以及一个进程中的所有线程(或 PTHREAD_SCOPE_PROCESS)。

系统争用范 围中的一个线程与系统中所有其他线程(包含其他进程中的那些线程)争用资源。 一个进程中的高优先级线程可阻止其他进程中的系统争用范围线程运行。

进 程争用范围内的线程在进程内进行调度,这表示只在一个进程内的所有线程间进行调度。 进程争用范围通常表示由操作系统选择要运行的进程,而进程本身包含一个内部调度程序来试图针对进程内的线程实现 POSIX 调度规则。
 四、测试源代码

在linux下我们可以通过

int pthread_create(pthread_t *thread
, const pthread_attr_t *attr
,
void *(*start_routine )(void*), void *arg );
来创建线程,但是如何设置线程的优先级呢?
在讨论这个问题的时候,我们先要确定当前线程使用的调度策略,posix提供了
int pthread_attr_getschedpolicy(const pthread_attr_t *attr , int *policy );函数来获取所
使用的调度策略,它们是:
SCHED_FIFO, SCHED_RR 和 SCHED_OTHER。
我们可以使用
int sched_get_priority_max(int policy );
int sched_get_priority_min(int policy );
来获取线程线程可是设置的最大和最小的优先级值,如果调用成功就返回最大和最小的优先级值,否则返回-1。
从我现在运行的linux系统中,我使用下列程序获取了对应三种调度策略中的最大和最小优先级:
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 是不支持优先级使用的,而SCHED_FIFO和SCHED_RR支持优先级的使用,他们分别为1和99,
数值越大
优先级越高。 从上面的结果我们可以看出,如果程序控制线程的优先级,一般是用
pthread_attr_getschedpolicy来获取系统使用的调度策略,如果是SCHED_OTHER的话,表明当前策略
不支持线程优先级的使用,否则可以。当然所设定的优先级范围必须在最大和最小值之间。我们可以通过
sched_get_priority_maxsched_get_priority_min来获取。
可能网友会问,是否我们可以通过
int pthread_attr_setschedpolicy(pthread_attr_t *attr , int policy );来设定自己所需的
调度策略呢?我觉得是完全可以的(有些系统需要定义
_POSIX_THREAD_PRIORITY_SCHEDULING),只要
系统实现了对应的调用策略。
说了半天,我们还没有说,在系统允许使用线程优先级别的时候,如何设置优先级别呢?
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 );

上面两个函数分别用于设置线程的优先级,struct sched_param的定义如下
struct sched_param
{
int __sched_priority; //所要设定的线程优先级
};


使用的测试程序:

#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, &param );
        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;
}

作者注:以上内容纯属拼凑而成,如果你没看明白,清直接看源地址。
直接引用文献(不一定是作者出 处):
1、
希望之光的博客
2、ITPUB论坛
3、IBM AIX文档
4、中国源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值