线程的基本概念和调度策略 (2007-04-28 11:21)
分类: linux学习
关键词:线程 进程 优先级 调度策略 时间片
一、线程的基本概念 进程(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,可随意由 nice 或 renice 命令进行设置)和其处理器使用的损失而变化。 线程的优先级可以固定成某个值,如果用 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)的使用不相关。
![]() 线程的 nice 值在创建线程时设置并且在线程的整个生命期中都是常量,除非用户通过 renice 命令或 setpri()、setpriority()、thread_setsched() 或 nice() 系统调用明确更改了它的值。 处理器损失是一个整数,它通过线程最近的处理器使用来计算。如果每次在一个 10 ms 的时钟滴答结束时线程受处理器控制,则最近的处理器使用值近似加 1,直到达到最大值 120。每个滴答的实际优先级损失随着 nice 的值增加。所有线程的最近处理器使用值每秒重算一次。 结果如下:
注: 使用多处理器运行队列及其负载平衡机制以后,nice 或 renice 的值对线程的优先级可能没有预期的影响,因为较低优先级的运行时间可能等于或大于较高优先级的运行时间。要求 nice 或 renice 产生预期效果的线程应该放在全局运行队列中。 三、线程调度策略 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下我们可以通过 #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; } 作者注:以上内容纯属拼凑而成,如果你没看明白,清直接看源地址。 直接引用文献(不一定是作者出处): 1、希望之光的博客 2、ITPUB论坛 3、IBM AIX文档 4、中国源码 |
|