Linux线程的调度策略
Linux线程的调度策略
Linux系统中所有的程序得到运行都会变为一个进程(process),而进程中可能会存在一个或者多个任务(task),这些任务就以进程中的线程(thread)作为载体,所以线程就是系统调度的最小单位。
线程的调度策略
A. SCHED_OTHER
SCHED_OTHER调度策略指的是分时调度策略,是Linux系统中默认的调度策略,一般采用CFS(Completely Fair Scheduler)算法,该算法为运行队列中的每一个进程都设置一个虚拟时钟vruntime(Virtual Runtime)
如果一个进程得到执行,随着执行时间的不断增长,其vruntime也将不断增大,没有得到执行的进程vruntime将保持不变。而调度器将会选择最小的vruntime那个进程来执行。这就是所谓的“完全公平”。不同优先级的进程其vruntime增长速度不同,优先级高的进程vruntime增长得慢,所以可能会得到更多的运行机会。
注意:如果一个线程以SCHED_OTHER调度策略进行创建,线程的静态优先级必须设置为0。
B. SCHED_FIFO
SCHED_FIFO调度策略指的是实时调度策略,采用“*先到先服务*”的调度方式,当一条线程采用该调度策略则会一直运行,直到它被更高优先级的线程抢占或者主动放弃CPU,才会交出控制权。
当线完成后,内核会去寻找处于就绪状态相同优先级的线程,如果不存在,则寻找低优先级线程。该调度策略实现了数据的互斥,在线程运行的时间内其他相同优先级线程无法进行资源抢占。
C. SCHED_RR
SCHED_RR(Round Robin,中文意为轮询)指的是实时调度策略,是一种基于*时间片轮转*的调度策略,它会给每个线程设置一个固定的优先级,并按照优先级顺序对线程进行轮流调度。
当一条线程采用该调度策略则会一直运行,直到它被更高优先级的线程抢占、主动放弃CPU以及消耗完自己的时间片,才会交出控制权。
时间片是线程运行的最小时间单元,由操作系统预先设定。当时间片用完时,该线程自动交出控制权,之后内核会按照和FIFO相同的方式搜索下一个工作线程。
轮转调度可以防止某一个任务连续占用太多的资源,而导致其他线程信息得不到及时处理。缺点是轮转调度会增大由于任务切换(任务上下文)而导致的开销。
优先级别
Linux系统中任务优先级分为两种:一种是静态优先级,一种是动态优先级,两者区别如下:
(1)静态优先级
静态优先级指的是任务一旦设置好优先级之后就不能再改变,相当于是任务本身的属性,通过Linux系统内核源码中的头文件sched.h可以知道,线程的优先级范围是0~99,优先级数值越大则优先级越高。
Linux系统中普通任务的优先级是0,系统任务的优先级是1~99,也就是说普通任务可能随时会被系统任务抢占。普通任务的静态优先级必须被设定为0,意味着普通任务无法跟系统任务参与系统资源的竞争,普通任务彼此之间的用动态优先级去竞争系统资源。
Linux系统中提供了两个函数接口来设置以及获取线程的继承调度属性,分别是pthread_attr_setinheritsched()和pthread_attr_getinheritsched()
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);//设置线程的继承属性
int pthread_attr_getinheritsched(const pthread_attr_t *attr,int *inheritsched);//查看线程的继承属性
第一个参数:attr指的是线程的属性对象,用户在创建线程之前可以先设置线程的属性,其中线程的属性就包含线程的调度属性,线程的属性对象attr在调用pthread_attr_init()函数初始化之后,*默认是继承*创建该线程的线程的调度属性。
第二个参数:inheritsched指的是线程的属性是打算继承创建该线程的线程的调度属性或者指定为线程属性对象attr中的调度属性,如果该参数为PTHREAD_EXPLICIT_SCHED,则表示线程的调度属性指定为attr的值。
注意:一条线程如果以默认的属性进行创建,则该线程的调度策略被设置为SCHED_OTHER,并且线程的优先级默认设置为0,也就意味着创建的线程属于普通任务。
Linux系统中线程创建时如果采用默认的属性,则线程的调度策略就是SCHED_OTHER,当然,Linux系统提供了一个名称叫做pthread_attr_setschedpolicy()的函数,用户可以利用该函数设置线程的调度属性。
练习:创建一条指定属性的线程,要求线程的调度策略为先到先服务策略,提示:创建属性对象 --> 初始化属性对象 --> 设置属性对象的继承调度属性(不继承) --> 设置调度策略!
#include <stdio.h>
#include <pthread.h>
void * task(void * arg)
{
}
int main()
{
//1.初始化线程的属性
pthread_attr_t attr;
pthread_attr_init(&attr);
//2.初始化之后设置线程的继承属性
int inheritsched;
pthread_attr_getinheritsched(&attr,&inheritsched);
//3.表达式成立,说明默认线程属性对象中默认是:继承
if(inheritsched=PTHREAD_INHERIT_SCHED)
{
printf("INHERIT\n");
}
else
{
printf("EXPLICIT\n");
}
//4.设置线程的属性,不继承
pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);
//5.修改线程的调度策略为先来先服务
pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
//6.创建线程
pthread_t thread;
pthread_create(&thread,&attr,task,NULL);
while(1)
{
}
return 0;
}
思考:用户设置好线程的调度策略后,如果准备对不同优先级的线程进行调度,请问如何来设置线程的静态优先级?
回答:Linux系统提供了一个名称叫做pthread_attr_setschedparam()的函数,用户利用该函数可以设置线程的优先级
练习:编写一个多线程程序,要求采用SCHED_FIFO调度策略创建两条相同静态优先级的线程,一个线程的任务是在死循环输出数字09,另一个线程的任务是在死循环输出字母az,观察其运行效果。提示:需要确保Linux系统的处理器数量和内核数量为1。
#include <stdio.h>
#include <pthread.h>
pthread_attr_t attr;
//子线程1 调度策略:FIFO 优先级:1
void *task1(void *arg)
{
//循环输出0~9
while(1)
{
int i=0;
if(i<10)
{
printf("%d\n",i);
i++;
}
else
{
i=0;
}
sleep(1);
}
}
//子线程2 调度策略:FIFO 优先级:2
void *task2(void *arg)
{
循环输出a~z
while(1)
{
char a='a';
if(a<=122)
{
printf("%c\n",a);
a++;
}
else
{
a='a';
}
sleep(5);
}
}
//主线程 也算是普通任务
int main()
{
//1.设置线程的调度策略为SCHED_FIFO
pthread_attr_t attr;
pthread-pthread_attr_setschedpolicy(&attr,SCHED_FIFO);
//2.设置优先级
struct sched_param param;
param.sched_priority = 1;
pthread_attr_setschedparam(&attr,¶m);
//6.创建子线程 子线程1的优先级是1
pthread_t thread1;
pthread_create(&thread1,&attr,task1,NULL);
//7.再次修改创建的子线程的静态优先级
param.sched_priority = 2;
pthread_attr_setschedparam(&attr,¶m);
//8.创建子线程 子线程2的优先级是2
pthread_t thread2;
pthread_create(&thread2,&attr,task2,NULL);
while(1)
{
pthread_exit(NULL);
}
return 0;
}
动态优先级
动态优先级指的是当多个普通任务并发运行时,系统会根据其实际运行的表现来动态地调整他们的nice值,任务的表示如下:
A. 睡眠时间越多,放着系统资源不用,系统就倾向于判定其为IO消耗性的任务,会逐步提高其优先级
B. 睡眠时间越少,拼命抢占系统资源,系统就倾向于判定其为CPU消耗性任务,会逐步降低其优先级
*注意:Linux系统中线程的**nice值越高,**则**优先级越低**,而**nice值越低,**反而**优先级越高**!!!*
思考:除了系统这种自动化的管理之外,用户能否通过代码的方式自主地干预线程的动态优先级? 如果可以干预,请问应该如何修改普通任务的动态优先级?
回答:用户可以修改普通任务的动态优先级,实际上就是修改普通任务的nice值即可,Linux系统提供了一个名字叫做nice()的函数接口
nice值的范围是-20 ~ 19,其中-20是最高优先级,19是最低优先级,所以nice值越小,则优先级越高。注意:进程的nice值默认为0,如果要降低nice值(即想要提高优先级),那么启动程序时必须加sudo,获得管理员的权限才能启动。
练习:编写一个多线程程序,要求创建两条相同静态优先级但不同动态优先级的的线程,一个线程的任务是在死循环输出数字,另一个线程的任务是在死循环输出字母,观察其运行效果。提示:需要确保Linux系统的处理器数量和内核数量为1。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//子线程1 调度策略:OTHER
void *task1(void *arg)
{
nice(19);
while(1)
{
char a='a';
if(a<=122)
{
printf("%c\n",a);
a++;
}
else
{
a='a';
}
sleep(5);
}
}
//子线程2 调度策略:OTHER
void *task2(void *arg)
{
nice(-20);
循环输出a~z
while(1)
{
char a='a';
if(a<=122)
{
printf("%c\n",a);
a++;
}
else
{
a='a';
}
sleep(5);
}
}
//主线程 也算是普通任务
int main(int argc, char const *argv[])
{
//1.创建子线程 普通任务
pthread_t thread1;
pthread_create(&thread1,NULL,task1,NULL);
//2.创建子线程
pthread_t thread2;
pthread_create(&thread2,NULL,task2,NULL);
pthread_exit(NULL);
return 0;
}