Linux线程的调度策略

  1. Linux线程的调度策略
  • 调度概念

Linux系统中所有的程序得到运行都会变为一个进程(process),而进程中可能会存在一个或者多个任务(task),这些任务就以进程中的线程(thread)作为载体,所以线程就是系统调度的最小单位。

而Linux系统的内核是抢占式的,也就是说线程得到CPU使用权并且开始执行之后不一定会顺利的执行完任务,而执行任务期间可能会被其他的任务抢夺CPU使用权,前提是其他任务的优先级比当前任务高的情况下。

当然,如果任务之间的优先级是相同的情况下,系统调度器也可以根据任务的先后顺序来分配CPU,每个任务执行一定的时间就切换到下一个任务,任务之间进行轮转,达到任务并发的目的。

  • 优先级别

Linux系统中任务优先级分为两种:一种是静态优先级,一种是动态优先级,两者区别如下:

  1. 静态优先级

静态优先级是指任务在创建时被赋予的优先级,它通常不会在任务运行过程中改变。在Linux系统中,静态优先级主要用于实时任务(Real-Time Tasks),这些任务需要在严格的时间约束内完成。

通过Linux系统内核源码中的头文件sched.h可以知道,线程的优先级范围是0~99,优先级数值越大则优先级越高。

Linux系统中普通任务的静态优先级是0,实时任务的静态优先级是1~99,实时任务通常用于对时间敏感的应用(如音频处理、实时控制等),需要在严格的时间约束内完成。

Linux系统中提供了两个函数接口来设置以及获取线程的继承调度属性,分别是pthread_attr_setinheritsched()和pthread_attr_getinheritsched(),使用规则如下所示:

第一个参数:attr指的是线程的属性对象,用户在创建线程之前可以先设置线程的属性,其中线程的属性就包含线程的调度属性,线程的属性对象attr在调用pthread_attr_init()函数初始化之后,默认是继承创建该线程的线程的调度属性。

第二个参数:inheritsched指的是线程的属性是打算继承创建该线程的线程的调度属性或者指定为线程属性对象attr中的调度属性,如果该参数为PTHREAD_EXPLICIT_SCHED,则表示线程的调度属性指定为attr的值。

注意:一条线程如果以默认的属性进行创建,则该线程的调度策略被设置为SCHED_OTHER,并且线程的优先级默认设置为0,也就意味着创建的线程属于普通任务。

思考:什么是线程的调度策略以及调度策略有几种? SCHED_OTHER调度策略是什么意思???

回答:Linux系统中线程的调度策略分三种:SCHED_OTHER、SCHED_FIFO、SCHED_RR,如下:

  1. SCHED_OTHER

SCHED_OTHER调度策略指的是分时调度策略,是Linux系统中默认的调度策略,一般采用CFS(Completely Fair Scheduler)算法,该算法为运行队列中的每一个进程都设置一个虚拟时钟vruntime(Virtual Runtime)。

如果一个进程得到执行,随着执行时间的不断增长,其vruntime也将不断增大,没有得到执行的进程vruntime将保持不变。而调度器将会选择最小的vruntime那个进程来执行。这就是所谓的“完全公平”。不同优先级的进程其vruntime增长速度不同,优先级高的进程vruntime增长得慢,所以可能会得到更多的运行机会。

注意:如果一个线程以SCHED_OTHER调度策略进行创建,线程的静态优先级必须设置为0。

  1. SCHED_FIFO

SCHED_FIFO调度策略指的是实时调度策略,采用“先到先服务”的调度方式,当一条线程采用该调度策略则会一直运行,直到它被更高优先级的线程抢占或者主动放弃CPU,才会交出控制权。

当线程完成后,内核会去寻找处于就绪状态相同优先级的线程,如果不存在,则寻找低优先级线程。该调度策略实现了数据的互斥,在线程运行的时间内其他相同优先级线程无法进行资源抢占。

这种调度策略确保了实时任务的快速响应和持续运行,但需要谨慎使用,因为高优先级的实时线程可能会导致低优先级线程饿死(starvation)。

  1. SCHED_RR

SCHED_RR(Round Robin,中文意为轮询)指的是实时调度策略,是一种基于时间片轮转的调度策略,它会给每个线程设置一个固定的优先级,并按照优先级顺序对线程进行轮流调度。

当一条线程采用该调度策略则会一直运行,直到它被更高优先级的线程抢占、主动放弃CPU以及消耗完自己的时间片,才会交出控制权。

时间片是线程运行的最小时间单元,由操作系统预先设定。当时间片用完时,该线程自动交出控制权,之后内核会按照和FIFO相同的方式搜索下一个工作线程。

SCHED_RR是一种适用于实时任务的调度策略,通过时间片轮转机制,它在保证实时性的同时,避免了低优先级任务的饥饿问题。缺点是轮转调度会增大由于任务切换(任务上下文)而导致的开销。

思考:既然Linux系统提供多种调度策略,那用户如何设置线程的调度属性为以上的三种呢?

回答:Linux系统中线程创建时如果采用默认的属性,则线程的调度策略就是SCHED_OTHER,当然,Linux系统提供了一个名称叫做pthread_attr_setschedpolicy()的函数,用户可以利用该函数设置线程的调度属性。

注意:如果用户准备设置线程的调度属性,则应该先调用pthread_attr_setinheritsched()函数,并设置线程属性对象attr的继承调度属性为PTHREAD_EXPLICIT_SCHED。

练习:创建一条指定属性的线程,要求线程的调度策略为先到先服务策略,提示:创建属性对象 --> 初始化属性对象 --> 设置属性对象的继承调度属性(不继承) --> 设置调度策略!

作业:了解一下如何利用linux系统提供了的和调度器相关的函数接口来实现让某个进程或者线程和处理器的某个核进行绑定,需要了解如何高效的利用多核处理器。

思考:用户设置好线程的调度策略后,如果准备对不同优先级的线程进行调度,请问如何来设置线程的静态优先级?

回答:Linux系统提供了一个名称叫做pthread_attr_setschedparam()的函数,用户利用该函数可以设置线程的优先级,使用规则如下:

注意:如果线程的调度策略是SCHED_OTHER,则线程的优先级必须设置为0,用于表示普通任务。

如果线程的调度策略是SCHED_FIFO或者SCHED_RR,则线程的优先级可以设置为1~99,表示实时任务。

注意:线程的优先级是1~99,则运行程序时必须获取超级用户(root)权限,比如 sudo ./xxx。

练习:编写一个多线程程序,要求采用SCHED_FIFO调度策略创建两条相同静态优先级的线程,一个线程的任务是在死循环输出数字0~9,另一个线程的任务是在死循环输出字母a~z,观察其运行效果。提示:需要确保Linux系统的处理器数量和内核数量为1。

练习:编写一个多线程程序,要求采用SCHED_FIFO调度策略创建两条不同静态优先级的线程,一个线程的任务是在死循环输出数字,另一个线程的任务是在死循环输出字母,观察其运行效果。提示:需要确保Linux系统的处理器数量和内核数量为1。

  1. 动态优先级

动态优先级指的是当多个普通任务并发运行时,系统会根据其实际运行的表现来动态地调整他们的nice值,任务的表示如下:

  1. 睡眠时间越多,放着系统资源不用,系统就倾向于判定其为IO消耗性的任务,会逐步提高其优先级
  1. 睡眠时间越少,拼命抢占系统资源,系统就倾向于判定其为CPU消耗性任务,会逐步降低其优先级

注意:Linux系统中线程的nice值越高,则优先级越低,而nice值越低,反而优先级越高!!!

思考:除了系统这种自动化的管理之外,用户能否通过代码的方式自主地干预线程的动态优先级? 如果可以干预,请问应该如何修改普通任务的动态优先级?

回答:用户可以修改普通任务的动态优先级,实际上就是修改普通任务的nice值即可,Linux系统提供了一个名字叫做nice()的函数接口,使用规则如下:

思考:nice函数的参数需要填写一个int型数值,但是在函数描述中并没有该值的范围,请问是否用户可以随意填写?

回答:不可以,nice值是有范围的,用户可以通过shell命令:nice 进行了解,如下图所示:

可以看到,nice值的范围是-20 ~ 19,其中-20是最高优先级,19是最低优先级,所以nice值越小,则优先级越高。

注意:进程的nice值默认为0,如果要降低nice值(即想要提高优先级),那么启动程序时必须加sudo,获得管理员的权限才能启动。

练习:设计一个程序,要求主线程以默认属性创建2个子线程,要求在每个子线程的任务中调用nice函数修改子线程的动态优先级,运行程序观察效果。

  1. Linux线程信号响应

思考:进程间通信中有种方案是通过信号通信,也就是一个进程可以向其他进程发送信号,如果接收到信号的进程中如果存在多条线程,请问是由哪条线程进行响应?

回答:由于多线程程序中的线程的执行状态是并发的,因此当一个进程收到一个信号时,那么由进程中的哪条线程响应这个信号就是不确定的,取决于哪条线程刚好在信号达到的瞬间被调度,这种不确定性在程序逻辑中一般是不能接收的。

所以可以在接收到信号的进程中指定一条线程对信号进行响应,让进程中的其他线程对该信号进行屏蔽即可。

  • 发送信号给线程

Linux系统中提供了一个名称叫做pthread_kill()的函数接口,用户利用该接口可以向指定的线程发送信号。

当然,如果用户打算给某条线程发送信号的同时还想要发送一个额外的数据,Linux系统提供了一个名称叫做pthread_sigqueue()的函数接口可以实现该功能。

  • 线程对信号屏蔽

如果进程中的其他线程不打算对某个信号进行响应,则可以对信号进行屏蔽,Linux系统提供了一个名称叫做sigprocmask()的函数接口可以实现该目的。

另外,Linux系统提供了一个名称叫做pthread_sigmask()的函数,该函数的功能和sigprocmask()一样,只不过是两者的标准不同。

练习:设计一个程序,要求程序中有3条子线程,子线程A接收到SIGINT信号时就输出自身的线程ID,子线程B和子线程C则屏蔽该信号,运行程序并观察效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值