抢占式任务调度和非抢占式(轮询任务调度)的区别,以及任务调度算法的用途。

时间:2022-07-17 20:17:23
1、说说轮巡任务调度与抢占式任务调度的区别?
答:轮询任务调度与抢占式任务调度的区别在于抢占式调度可以因为优先级高的任务抢占cpu,而轮询的不能。
2当软件线程个数超过硬件线程个数的时候,支持抢占式多任务处理的操作系统一般会采用时间片轮转调度的方案。
3 对于RTOS中,理解这两个概念是很重要的。实时系统对于响应时间是有非常严格的要求,尤其是在硬实时系统中,没有满足响应时间的上限将视为系统失败。影响RTOS响应时间的一个重要方面就是任务调度算法。在 RTOS中,主要的调度算法是基于任务优先级的抢占式调度。在这种调度算法中,系统总是选择优先级别的最高的算法进行调度,并且 一旦高优先级别的任务准备就绪之后,它就会马上被调度而不等待低优先级的任务主动放弃CPU。这和通用OS的时间片轮转调度算法是 不一样的,在时间片轮转调度算法中,只有等任务主动放弃CPU,高优先级的任务才有调度的优先权。在基于任务优先级抢占式调度算法中,会产生一个优先级反转问题。解决这个问题的方式主要包括继承优先级和天花板策略。继承优先级策略是一旦高优先级的任务所需要的竞争资源被低优先级的任务使用,就提高低优先级的任务的优先级别,从而使得能使竞争资源尽快释放。天花板策略是在创建信号量的时候,根据可能使用该信号量所有的任务的最高优先级别来设置当前任务的优先级,这个优先级是由创建资源的用户来决定。
4很多编程人员都认为,使用多线程能够提升程序的性能,如果少量的线程能够提升程序的性能,他们就会认为更多的线程能够更好。但实际上,多线程只是为不同的程序比较合理地安排运行时间,更加充分的利用系统资源,这当中存在着一个线程数和程序性能的平衡,过多的线程可能会严重影响程序的性能。这种影响主要有以下两个方面:首先,将给定的工作量划分给过多的线程会造成每个线程的工作量过少,因此可能导致线程启动和终止时的开销比程序实际工作的开销还要多;其次,过多并发线程的存在将导致共享有限硬件资源的开销增大。

操作系统使用进程将它们正在执行的不同应用程序分开。线程是操作系统分配处理器时间的基本单元,并且进程中可以有多个线程同时执行代码。每个线程都维护异常处理程序、调度优先级和一组系统用于在调度该线程前保存线程上下文的结构。线程上下文包括为使线程在线程的宿主进程地址空间中无缝地继续执行所需的所有信息,包括线程的 CPU 寄存器组和堆栈。
当软件线程个数超过硬件线程个数的时候,支持抢占式多任务处理的操作系统一般会采用时间片轮转调度的方案。它通过以下方式实现这一点:在需要硬件线程时间的软件线程之间分割可用硬件线程时间,并轮流为每个线程分配硬件线程时间片(time slice)。当前执行的软件线程在其时间片结束时被挂起,而另一个软件线程继续运行。当系统从一个软件线程切换到另一个软件线程时,它将保存被抢占的软件线程的线程上下文,并重新加载线程队列中下一个软件线程的已保存线程上下文。
时间片的长度取决于操作系统和处理器。由于每个时间片都很小,因此即使只有一个处理器,多个线程看起来似乎也是在同时执行。这实际上就是多处理器系统中发生的情形,在此类系统中,可执行线程分布在多个可用处理器中。
时间片机制保证了所有的软件线程都能够被执行,这样也就避免了某些软件线程长期占据硬件线程,而导致其他的软件线程出现饥饿(starvation)的情况。但是,操作系统的时间片轮转调度方案也将引入额外的开销,随着软件线程的增多,这种开销将会急剧增加,进而降低系统性能。这种开销主要有以下几种:
首先是线程间切换时保存和恢复进程寄存器的开销。在系统要进行线程间切换的时候,需要保存当前线程的寄存器状态,以便在下次回调到当前线程的时候,能够恢复线程寄存器继续运行。当存在少量线程的时候,进程调度程序会给每一个线程分配足够长的时间片,相比较之下,保存和恢复线程寄存器的开销变得不是很显著。但是随着线程数目的增加,线程调度程序分给每个线程的时间片也会相应减少,而保存和恢复线程寄存器的开销不变,这样,在每个时间片当中,系统将更多的时间用于保存和恢复线程寄存器,就会显著地降低系统性能。
另一方面,在使用时间片机制的时候,保存和恢复线程使用的cache的开销则是更敏感的一种开销。现代体系结构中,cache通常比主存快10到100倍,CPU访问cache并命中对于系统性能的提高具有非常重要的作用。而当存在线程切换的时候,新的线程要访问的数据尚未装入cache,CPU需要从主存中读取信息的同时,cache替换策略把该地址所在的那块存储内容从主存拷贝到cache中。一般情况下,cache替换策略是使用最近最少使用策略(LRU)选择的,LRU策略是把当前近期cache中使用次数最少的那块数据块替换出去,而这些数据块中的数据很可能就是上几个时间片的线程使用的。这样,各个线程就在不断地竞争cache,相互淘汰彼此使用的cache数据,不断造成cache扑空,最终降低了系统性能。
此外,在更底层的存储器结构上也存在相似的问题,那就是线程对主存的争夺。现代大部分操作系统都实现了虚拟内存。一台机器上同时运行着很多程序,这些程序所需的存储空间可能比存储器的实际容量要大得多,但是在每个时间点,存储器只有一部分被激活。主存只需存放众多程序中的活跃部分,就像cache中只存放一个程序的活跃部分一样,而其他的部分则存储到磁盘上。由于每个线程都有自己的栈空间和私有数据结构。类似于对cache的争夺,随着线程数的增加,线程之间就会争夺主存,导致性能上的损失。
以上几种问题是由于线程对共享资源的争夺产生的,另外还存在一个性质不同,但是后果可能更加严重的问题,称为护航(convoying),它是指线程聚集在一起,等待获取某一个锁。当某个线程持有一个锁的时候,用完了自己的时间片,这时所有等待锁的线程都必须等待这个线程被唤醒并且释放锁。
最好的解决方法是依据实际情况,使用尽可能少的线程,这样可以最大限度地减少操作系统资源的使用,并可提高性能。千万不要硬性规定线程的个数,而是将其作为一个可调节的参数。
操作系统中存在两种类型的线程,I/O阻塞线程和计算非阻塞线程。许多应用程序,例如终端机模拟程序,都需要在同一时间处理对一个以上的文件的读写操作,我们不可能依次地等待每个请求完成才继续处理下一个请求,而是让所有这些I/O操作并行处理,并且当任何一个I/O完成时,应用程序会收到一个事件通告,这种处理I/O的线程称为I/O阻塞线程。而另外一些进程,例如进行大量计算,处理图形的线程则称为计算非阻塞线程。I/O阻塞线程不会引起时间片切换开销,而计算非阻塞线程则会引起时间片切换的开销。所以,将I/O阻塞线程和计算非阻塞线程分离开是一种非常好的组织方法。计算非阻塞线程在大多数时间内都是被调度函数调度到的,应该和处理器资源相匹配,而I/O阻塞线程在大多数时间内都在等待外部事件。
由于构造良好的多线程应用程序要多方面地考虑资源要求和潜在冲突,需要相当的专业技术,所以一般最好采用现成的软件来完成,下面的一些经验是很有帮助的:
建议您尽量使用OpenMP。OpenMP提供了一种简单的方法,供程序员描述要并行的循环迭代,而不用创建具体数目的线程,OpenMP可以根据目标系统尽量使用最优数量的线程个数。
建议您使用线程池。每个传入的请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理。一旦池中的某个线程完成任务,它将返回到等待线程队列中,等待被再次使用,这种重用使应用程序可以避免为每个线程创建新进程的开销。线程池通常具有最大线程数限制,如果所有线程都繁忙,而额外的任务将放入队列中,直到有线程可用时才能够得到处理。当您不需要给一个任务设定特定的优先级,当不会有可能会运行很长时间的任务(并因此阻塞了其他任务),我们建议您使用线程池,技术高超的软件设计专家可能希望编写自己的任务调度程序,一般都采用任务窃取(work stealing)的方法,它是指每个线程都有自己私有的任务集合,当一个线程执行完自己的任务以后,它就从其他线程的任务集合中窃取任务来执行。任务窃取实现了较好的cache利用率和负载平衡。当一个线程运行自己的任务时,它往往会重用自己cache中的数据,当它完成自己的任务,就需要窃取任务来运行,这实际上就是一种负载平衡。高效的任务策略在于窃取目标的选择,选择较大的任务能够使窃取者忙碌相当长一段时间。早期的Clik调度程序(Blumofe,1995年)就是一个关于如何编写高效的任务窃取调度程序的范例。