精华内容
下载资源
问答
  • 调度仅仅是针对作业而运行机制才区分map任务和reduce任务
  • Quartz任务调度机制

    千次阅读 2014-03-17 14:05:45
    Quartz任务调度机制 1 概述 1.1 了解Quartz体系结构 Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述: ●Job...
    

    Quartz任务调度机制

    概述

    1.1 了解Quartz体系结构

    Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述:

    Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;


    JobDetailQuartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;


    Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTriggerCronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;


    Calendarorg.quartz.Calendarjava.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartzorg.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendarMonthlyCalendarWeeklyCalendar分别针对每年、每月和每周进行定义;


    Scheduler:代表一个Quartz的独立运行容器,TriggerJobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中TriggerJobDetail


    Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,JobTrigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;


    ThreadPoolScheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

    Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。


    正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。


    Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对TriggerJobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。


    Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。


    1描述了Scheduler的内部组件结构,SchedulerContext提供Scheduler全局可见的上下文信息,每一个任务都对应一个JobDataMap,虚线表达的JobDataMap表示对应有状态的任务:

     

    1 Scheduler内部结构

     

    一个Scheduler可以拥有多个Triger组和多个JobDetail组,注册TriggerJobDetail时,如果不显式指定所属的组,Scheduler将放入到默认组中,默认组的组名为Scheduler.DEFAULT_GROUP。组名和名称组成了对象的全名,同一类型对象的全名不能相同。


    Scheduler本身就是一个容器,它维护着Quartz的各种组件并实施调度的规则。Scheduler还拥有一个线程池,线程池为任务提供执行线程——这比执行任务时简单地创建一个新线程要拥有更高的效率,同时通过共享节约资源的占用。通过线程池组件的支持,对于繁忙度高、压力大的任务调度,Quartz将可以提供良好的伸缩性。


    提示: Quartz完整下载包examples目录下拥有10多个实例,它们是快速掌握Quartz应用很好的实例。

    使用SimpleTrigger

    SimpleTrigger拥有多个重载的构造函数,用以在不同场合下构造出对应的实例:

    SimpleTrigger(String name, String group):通过该构造函数指定Trigger所属组和名称;

    SimpleTrigger(String name, String group, Date startTime):除指定Trigger所属组和名称外,还可以指定触发的开发时间;

    SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval):除指定以上信息外,还可以指定结束时间、重复执行次数、时间间隔等参数;

    SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval):这是最复杂的一个构造函数,在指定触发参数的同时,还通过jobGroupjobName,让该TriggerScheduler中的某个任务关联起来。

    通过实现 org.quartz..Job 接口,可以使 Java 类化身为可调度的任务。代码清单1提供了 Quartz 任务的一个示例:


    代码清单1 SimpleJob:简单的Job实现类

     

    这个类用一条非常简单的输出语句实现了Job接口的execute(JobExecutionContext context) 方法,这个方法可以包含想要执行的任何代码。下面,我们通过SimpleTriggerSimpleJob进行调度:


    代码清单2 SimpleTriggerRunner:使用SimpleTrigger进行调度

     

    在①处通过JobDetail封装SimpleJob,同时指定JobScheduler中所属组及名称,这里,组名为jGroup1,而名称为job1_1

    在②处创建一个SimpleTrigger实例,指定该TriggerScheduler中所属组及名称。接着设置调度的时间规则。

    最后,需要创建Scheduler实例,并将JobDetailTrigger实例注册到Scheduler中。这里,我们通过StdSchedulerFactory获取一个Scheduler实例,并通过scheduleJob(JobDetail jobDetail, Trigger trigger)完成以下两件事:

            a. JobDetailTrigger注册到Scheduler中;

            b. Trigger指派给JobDetail,将两者关联起来。

    Scheduler启动后,Trigger将定期触发并执行SimpleJobexecute(JobExecutionContext jobCtx)方法,然后每 10 秒重复一次,直到任务被执行 100 次后停止。还可以通过SimpleTriggersetStartTime(java.util.Date startTime)setEndTime(java.util.Date endTime)指定运行的时间范围,当运行次数和时间范围冲突时,超过时间范围的任务运行不被执行。如可以通过simpleTrigger.setStartTime(new Date(System.currentTimeMillis() + 60000L))指定60秒钟以后开始。

    除了通过scheduleJob(jobDetail, simpleTrigger)建立TriggerJobDetail的关联,还有另外一种关联TriggerJobDetail的方式:


     

     

    在这种方式中,Trigger通过指定Job所属组及Job名称,然后使用SchedulerscheduleJob(Trigger trigger)方法注册Trigger。有两个值得注意的地方:

            a. 通过这种方式注册的Trigger实例必须已经指定Job组和Job名称,否则调用注册Trigger的方法将抛出异常;

            b. 引用的JobDetail对象必须已经存在于Scheduler中。也即,代码中①、②和③的先后顺序不能互换。

    在构造Trigger实例时,可以考虑使用org.quartz.TriggerUtils工具类,该工具类不但提供了众多获取特定时间的方法,还拥有众多获取常见Trigger的方法,如makeSecondlyTrigger(String trigName)方法将创建一个每秒执行一次的Trigger,而makeWeeklyTrigger(String trigName, int dayOfWeek, int hour, int minute)将创建一个每星期某一特定时间点执行一次的Trigger。而getEvenMinuteDate(Date date)方法将返回某一时间点一分钟以后的时间。

    使用CronTrigger

    CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。因此,相对于SimpleTrigger而言,CronTrigger在使用上也要复杂一些。

    3.1 Cron表达式

    Quartz使用类似于Linux下的Cron表达式定义时间规则,Cron表达式由67个由空格分隔的时间字段组成,如表1所示:

    1 Cron表达式时间字段

    位置

    时间域名

    允许值

    允许的特殊字符

    1

    0-59

    , - * /

    2

    分钟

    0-59

    , - * /

    3

    小时

    0-23

    , - * /

    4

    日期

    1-31

    , - * ? / L W C

    5

    月份

    1-12

    , - * /

    6

    星期

    1-7

    , - * ? / L C #

    7

    (可选)

    空值1970-2099

    , - * /

     

    Cron表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能,细说如下:

    ●星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;

    ●问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;

    ●减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从1012点,即10,11,12

    ●逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;

    ●斜杠(/)x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,3045秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y

    L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;

    W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期范围;

    LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;

    ●井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;

    ● C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

    Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。

    2下面给出一些完整的Cron表示式的实例:

    2 Cron表示式示例

    表示式

    说明

    "0 0 12 * * ? "

    每天12点运行

    "0 15 10 ? * *"

    每天10:15运行

    "0 15 10 * * ?"

    每天10:15运行

    "0 15 10 * * ? *"

    每天10:15运行

    "0 15 10 * * ? 2008"

    2008年的每天1015运行

    "0 * 14 * * ?"

    每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59

    "0 0/5 14 * * ?"

    每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55

    "0 0/5 14,18 * * ?"

    每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。

    "0 0-5 14 * * ?"

    每天14:00点到14:05,每分钟运行一次。

    "0 10,44 14 ? 3 WED"

    3月每周三的14:10分到14:44,每分钟运行一次。

    "0 15 10 ? * MON-FRI"

    每周一,二,三,四,五的10:15分运行。

    "0 15 10 15 * ?"

    每月1510:15分运行。

    "0 15 10 L * ?"

    每月最后一天10:15分运行。

    "0 15 10 ? * 6L"

    每月最后一个星期五10:15分运行。

    "0 15 10 ? * 6L 2007-2009"

    2007,2008,2009年每个月的最后一个星期五的10:15分运行。

    "0 15 10 ? * 6#3"

    每月第三个星期五的10:15分运行。

    4 CronTrigger实例

    下面,我们使用CronTriggerSimpleJob进行调度,通过Cron表达式制定调度规则,让它每5秒钟运行一次:

    代码清单3 CronTriggerRunner:使用CronTrigger进行调度


    package com.baobaotao.basic.quartz;

    import org.quartz.CronExpression;

    import org.quartz.CronTrigger;

    import org.quartz.JobDetail;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerFactory;

    import org.quartz.impl.StdSchedulerFactory;

    public class CronTriggerRunner {

    public static void main(String args[]) {

    try {

    JobDetail jobDetail 
    = new JobDetail("job1_2""jGroup1",SimpleJob.class);

    -1:创建CronTrigger,指定组及名称

    CronTrigger cronTrigger 
    = new CronTrigger("trigger1_2""tgroup1");

    CronExpression cexp 
    = new CronExpression("0/5 * * * * ?");①-2:定义Cron表达式

    cronTrigger.setCronExpression(cexp);①
    -3:设置Cron表达式

    SchedulerFactory schedulerFactory 
    = new StdSchedulerFactory();

    Scheduler scheduler 
    = schedulerFactory.getScheduler();

    scheduler.scheduleJob(jobDetail, cronTrigger);

    scheduler.start();

    //

    }
     catch (Exception e) {

    e.printStackTrace();

    }


    }


    }

     

    运行CronTriggerRunner,每5秒钟将触发运行SimpleJob一次。默认情况下Cron表达式对应当前的时区,可以通过CronTriggerRunnersetTimeZone(java.util.TimeZone timeZone)方法显式指定时区。你还也可以通过setStartTime(java.util.Date startTime)setEndTime(java.util.Date endTime)指定开始和结束的时间。

    在代码清单3的②处需要通过Thread.currentThread.sleep()的方式让主线程睡眠,以便调度器可以继续工作执行任务调度。否则在调度器启动后,因为主线程马上退出,也将同时引起调度器关闭,调度器中的任务都将相应销毁,这将导致看不到实际的运行效果。在单元测试的时候,让主线程睡眠经常使用的办法。对于某些长周期任务调度的测试,你可以简单地调整操作系统时间进行模拟。

    使用Calendar

    在实际任务调度中,我们不可能一成不变地按照某个周期性的调度规则运行任务,必须考虑到实现生活中日历上特定日期,就象习惯了大男人作风的人在214号也会有不同表现一样。

    下面,我们安排一个任务,每小时运行一次,并将五一节和国际节排除在外,其代码如代码清单4所示

    代码清单4 CalendarExample:使用Calendar

     

    package com.baobaotao.basic.quartz;

    import java.util.Calendar;

    import java.util.Date;

    import java.util.GregorianCalendar;

    import org.quartz.impl.calendar.AnnualCalendar;

    import org.quartz.TriggerUtils;



    public class CalendarExample {

    public static void main(String[] args) throws Exception {

    SchedulerFactory sf 
    = new StdSchedulerFactory();

    Scheduler scheduler 
    = sf.getScheduler();

    ①法定节日是以每年为周期的,所以使用AnnualCalendar

    AnnualCalendar holidays 
    = new AnnualCalendar();

    ②五一劳动节

    Calendar laborDay 
    = new GregorianCalendar();

    laborDay.add(Calendar.MONTH,
    5);

    laborDay.add(Calendar.DATE,
    1);

    holidays.setDayExcluded(laborDay, 
    true); ②-1:排除的日期,如果设置为false则为包含

    ③国庆节

    Calendar nationalDay 
    = new GregorianCalendar();

    nationalDay.add(Calendar.MONTH,
    10);

    nationalDay.add(Calendar.DATE,
    1);

    holidays.setDayExcluded(nationalDay, 
    true);③-1:排除该日期

    scheduler.addCalendar(
    "holidays", holidays, falsefalse);④向Scheduler注册日历

    Date runDate 
    = TriggerUtils.getDateOf(0,01014);⑤4月1号 上午10点

    JobDetail job 
    = new JobDetail("job1""group1", SimpleJob.class);

    SimpleTrigger trigger 
    = new SimpleTrigger("trigger1""group1"

    runDate, 

    null

    SimpleTrigger.REPEAT_INDEFINITELY, 

    60L * 60L * 1000L);

    trigger.setCalendarName(
    "holidays");⑥让Trigger应用指定的日历规则

    scheduler.scheduleJob(job, trigger);

    scheduler.start();

    //实际应用中主线程不能停止,否则Scheduler得不到执行,此处从略

    }


    }

    由于节日是每年重复的,所以使用org.quartz.CalendarAnnualCalendar实现类,通过②、③的代码,指定五一和国庆两个节日并通过AnnualCalendar#setDayExcluded(Calendar day, boolean exclude)方法添加这两个日期。excludetrue时表示排除指定的日期,如果为false时表示包含指定的日期。

    在定制好org.quartz.Calendar后,还需要通过Scheduler#addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)进行注册,如果updateTriggerstrueScheduler中已引用CalendarTrigger将得到更新,如④所示。

    在⑥处,我们让一个Trigger指定使用Scheduler中代表节日的Calendar,这样Trigger就会避开五一和国庆这两个特殊日子了。

    任务调度信息存储

    在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。

    比如我们希望安排一个执行100次的任务,如果执行到50次时系统崩溃了,系统重启时任务的执行计数器将从0开始。在大多数实际的应用中,我们往往并不需要保存任务调度的现场数据,因为很少需要规划一个指定执行次数的任务。

    对于仅执行一次的任务来说,其执行条件信息本身应该是已经持久化的业务数据(如锁定到期解锁任务,解锁的时间应该是业务数据),当执行完成后,条件信息也会相应改变。当然调度现场信息不仅仅是记录运行次数,还包括调度规则、JobDataMap中的数据等等。

    如果确实需要持久化任务调度信息,Quartz允许你通过调整其属性文件,将这些信息保存到数据库中。使用数据库保存任务调度信息后,即使系统崩溃后重新启动,任务的调度信息将得到恢复。如前面所说的例子,执行50次崩溃后重新运行,计数器将从51开始计数。使用了数据库保存信息的任务称为持久化任务。

    通过配置文件调整任务调度信息的保存策略

    其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。

    先来了解一下Quartz的默认属性配置文件:

    代码清单5 quartz.properties:默认配置

    ①集群的配置,这里不使用集群

    org.quartz.scheduler.instanceName = DefaultQuartzScheduler

    org.quartz.scheduler.rmi.export = false

    org.quartz.scheduler.rmi.proxy = false

    org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

    ②配置调度器的线程池

    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

    org.quartz.threadPool.threadCount = 10

    org.quartz.threadPool.threadPriority = 5

    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

    ③配置任务调度现场数据保存机制

    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

    Quartz的属性配置文件主要包括三方面的信息:

    1)集群信息;

    2)调度器线程池;

    3)任务调度现场数据的保存。

    如果任务数目很大时,可以通过增大线程池的大小得到更好的性能。默认情况下,Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,顾名思义,信息保存在RAM内存中,我们可以通过以下设置将任务调度现场数据保存到数据库中:

    代码清单6 quartz.properties:使用数据库保存任务调度现场数据

    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

    org.quartz.jobStore.tablePrefix = QRTZ_①数据表前缀

    org.quartz.jobStore.dataSource = qzDS②数据源名称

    ③定义数据源的具体属性

    org.quartz.dataSource.qzDS.driver = oracle.jdbc.driver.OracleDriver

    org.quartz.dataSource.qzDS.URL = jdbc:oracle:thin:@localhost:1521:ora9i

    org.quartz.dataSource.qzDS.user = stamen

    org.quartz.dataSource.qzDS.password = abc

    org.quartz.dataSource.qzDS.maxConnections = 10

    要将任务调度数据保存到数据库中,就必须使用org.quartz.impl.jdbcjobstore.JobStoreTX代替原来的org.quartz.simpl.RAMJobStore并提供相应的数据库配置信息。首先①处指定了Quartz数据库表的前缀,在②处定义了一个数据源,在③处具体定义这个数据源的连接信息。

    你必须事先在相应的数据库中创建Quartz的数据表(共8张),在Quartz的完整发布包的docs/dbTables目录下拥有对应不同数据库的SQL脚本。

    查询数据库中的运行信息

    任务的现场保存对于上层的Quartz程序来说是完全透明的,我们在src目录下编写一个如代码清单6所示的quartz.properties文件后,重新运行代码清单2或代码清单3的程序,在数据库表中将可以看到对应的持久化信息。当调度程序运行过程中途停止后,任务调度的现场数据将记录在数据表中,在系统重启时就可以在此基础上继续进行任务的调度。

    代码清单7 JDBCJobStoreRunner:从数据库中恢复任务的调度


    package com.baobaotao.basic.quartz;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerFactory;

    import org.quartz.SimpleTrigger;

    import org.quartz.Trigger;

    import org.quartz.impl.StdSchedulerFactory;

    public class JDBCJobStoreRunner {

    public static void main(String args[]) {

    try {

    SchedulerFactory schedulerFactory 
    = new StdSchedulerFactory();

    Scheduler scheduler 
    = schedulerFactory.getScheduler();

    ①获取调度器中所有的触发器组

    String[] triggerGroups 
    = scheduler.getTriggerGroupNames();

    ②重新恢复在tgroup1组中,名为trigger1_1触发器的运行

    for (int i = 0; i < triggerGroups.length; i++{

    String[] triggers 
    = scheduler.getTriggerNames(triggerGroups[i]);

    for (int j = 0; j < triggers.length; j++{

    Trigger tg 
    = scheduler.getTrigger(triggers[j],triggerGroups[i]);

    if (tg instanceof SimpleTrigger

    && tg.getFullName().equals("tgroup1.trigger1_1")) {②-1:根据名称判断

    -1:恢复运行

    scheduler.rescheduleJob(triggers[j], triggerGroups[i],tg);

    }


    }


    }


    scheduler.start();

    }
     catch (Exception e) {

    e.printStackTrace();

    }


    }


    }

     

    当代码清单2中的SimpleTriggerRunner执行到一段时间后非正常退出,我们就可以通过这个JDBCJobStoreRunner根据记录在数据库中的现场数据恢复任务的调度。Scheduler中的所有Trigger以及JobDetail的运行信息都会保存在数据库中,这里我们仅恢复tgroup1组中名称为trigger1_1的触发器,这可以通过如②-1所示的代码进行过滤,触发器的采用GROUP.TRIGGER_NAME的全名格式。通过Scheduler#rescheduleJob(String triggerName,String groupName,Trigger newTrigger)即可重新调度关联某个Trigger的任务。

    下面我们来观察一下不同时期qrtz_simple_triggers表的数据:

    1.运行代码清单2SimpleTriggerRunner一小段时间后退出:

     

    REPEAT_COUNT表示需要运行的总次数,而TIMES_TRIGGER表示已经运行的次数。

    2.运行代码清单7JDBCJobStoreRunner恢复trigger1_1的触发器,运行一段时间后退出,这时qrtz_simple_triggers中的数据如下:

     

    首先Quartz会将原REPEAT_COUNT-TIMES_TRIGGER得到新的REPEAT_COUNT值,并记录已经运行的次数(重新从0开始计算)。

    3.重新启动JDBCJobStoreRunner运行后,数据又将发生相应的变化: 

    4.继续运行直至完成所有剩余的次数,再次查询qrtz_simple_triggers表:

     

    这时,该表中的记录已经变空。

    值得注意的是,如果你使用JDBC保存任务调度数据时,当你运行代码清单2SimpleTriggerRunner然后退出,当再次希望运行SimpleTriggerRunner时,系统将抛出JobDetail重名的异常:

    Unable to store Job with name: 'job1_1' and group: 'jGroup1', because one already exists with this identification.

    因为每次调用Scheduler#scheduleJob()时,Quartz都会将JobDetailTrigger的信息保存到数据库中,如果数据表中已经同名的JobDetailTrigger,异常就产生了。

    本文使用quartz 1.6版本,我们发现当后台数据库使用MySql时,数据保存不成功,该错误是Quartz的一个Bug,相信会在高版本中得到修复。因为HSQLDB不支持SELECT * FROM TABLE_NAME FOR UPDATE的语法,所以不能使用HSQLDB数据库。

    小结

    Quartz提供了最为丰富的任务调度功能,不但可以制定周期性运行的任务调度方案,还可以让你按照日历相关的方式进行任务调度。Quartz框架的重要组件包括JobJobDetailTriggerScheduler以及辅助性的JobDataMapSchedulerContext

    Quartz拥有一个线程池,通过线程池为任务提供执行线程,你可以通过配置文件对线程池进行参数定制。Quartz的另一个重要功能是可将任务调度信息持久化到数据库中,以便系统重启时能够恢复已经安排的任务。此外,Quartz还拥有完善的事件体系,允许你注册各种事件的监听器。

     

    参考链接:

    http://www.blogjava.net/baoyaer/articles/155645.html

    展开全文
  • UCOS之任务调度机制

    千次阅读 2011-07-29 11:38:01
    UCOS之任务调度机制本文引用自tianwaike1《UCOS之任务调度机制》一. 内核概述: 多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯。内核提供的基本服务是任务切换。之...

    UCOS之任务调度机制

    本文引用自tianwaike1《UCOS之任务调度机制》

    一. 内核概述:

        多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯。内核提供的基本服务是任务切换。之所以使用实时内核可以大大简化应用系统的设计,是因为实时内核允许将应用分成若干个任务,由实时内核来管理它们。内核本身也增加了应用程序的额外负荷,代码空间增加ROM的用量,内核本身的数据结构增加了RAM的用量。但更主要的是,每个任务要有自己的栈空间,这一块吃起内存来是相当厉害的。内核本身对CPU的占用时间一般在 2到5个百分点之间。uCOS II有一个精巧的内核调度算法,实时内核精小,执行效率高,算法巧妙,代码空间很少。

    二. uCOS II内核调度特点:

    1. 只支持基于优先级的抢占式调度算法,不支持时间片轮训;
    2. 64个优先级,只能创建64个任务,用户只能创建56个任务;
    3. 每个任务优先级都不相同。
    4. 不支持优先级逆转;
    5. READY队列通过内存映射表实现快速查询。效率非常高;
    6. 支持时钟节拍;
    7. 支持信号量,消息队列,事件控制块,事件标志组,消息邮箱任务通讯机制;
    8. 支持中断嵌套,中断嵌套层数可达255层,中断使用当前任务的堆栈保存上下文;
    9. 每个任务有自己的堆栈,堆栈大小用户自己设定;
    10. 支持动态修改任务优先级;
    11. 任务TCB为静态数组,建立任务只是从中获得一个TCB,不用动态分配,释放内存;
    12. 任务堆栈为用户静态或者动态创建,在任务创建外完成,任务创建本身不进行动态内存分配;
    13. 任务的总个数(OS_MAX_TASKS)由用户决定;
    14. 0优先级最高,63优先级最低;
    15. 有一个优先级最低的空闲任务,在没有用户任务运行的时候运行.

    三. 任务控制块 OS_TCB描述:

        uCOS II的TCB数据结构简单,内容容易理解,保存最基本的任务信息,同时还支持裁减来减小内存消耗,TCB是事先根据用户配置,静态分配内存的结构数组,通过优先级序号进行添加,查找,删除等功能。减少动态内存分配和释放。因为依靠优先级进行TCB分配,每个任务必须有自己的优先级,不能和其他任务具有相同的优先级。

    typedef struct os_tcb

    {

        OS_STK        *OSTCBStkPtr;

    #if OS_TASK_CREATE_EXT_EN

        void          *OSTCBExtPtr;

        OS_STK        *OSTCBStkBottom;

        INT32U         OSTCBStkSize;

        INT16U         OSTCBOpt;

        INT16U         OSTCBId;

    #endif

        struct os_tcb *OSTCBNext;

        struct os_tcb *OSTCBPrev;

    #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN

        OS_EVENT      *OSTCBEventPtr;

    #endif 

    #if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN

        void          *OSTCBMsg;

    #endif 

        INT16U         OSTCBDly;

        INT8U          OSTCBStat;

        INT8U          OSTCBPrio;

        INT8U          OSTCBX;

        INT8U          OSTCBY;

        INT8U          OSTCBBitX;

        INT8U          OSTCBBitY;

    #if OS_TASK_DEL_EN

        BOOLEAN        OSTCBDelReq;

    #endif

    } OS_TCB;

    .OSTCBStkPtr是指向当前任务栈顶的指针。

    .*OSTCBExtPtr;:任务扩展模块使用;

    .*OSTCBStkBottom;

    .OSTCBStkSize; .

    .OSTCBOpt;

    .OSTCBId;

    .OSTCBNext和.OSTCBPrev用于任务控制块OS_TCBs的双重链接,

    .OSTCBEventPtr是指向事件控制块的指针

    .OSTCBMsg是指向传给任务的消息的指针。

    .OSTCBDly当需要把任务延时若干时钟节拍时要用到这个变量,或者需要把任务挂起一段时间以等待某事件的发生,

    .OSTCBStat是任务的状态字。

    .OSTCBPrio是任务优先级。

    .OSTCBX, .OSTCBY, .OSTCBBitX和 .OSTCBBitY用于加速任务进入就绪态的过程或进入等待事件发生状态的过程

    OSTCBY = priority >> 3;

    OSTCBBitY = OSMapTbl[priority >> 3];

    OSTCBX = priority & 0x07;

    OSTCBBitX = OSMapTbl[priority & 0x07];

    .OSTCBDelReq是一个布尔量,用于表示该任务是否需要删除

    四. 就绪表(Ready List):

        uCOS II采用内存映射的方式来实现READY队列的加入,查找,删除功能,效率非常高。但是也因此只能支持64个任务,每个任务都有自己的优先级,不能和其他任务优先级相同。

     

        每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRdyGrp和OSRdyTbl[]。(OSRdyGrp占用一个字节:8位,每位代表一个优先级组;OSRdyTbl[]占8个字节,共64位,每位代表一个优先级)在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PRIO(见文件OS_CFG.H)。

         为确定下次该哪个优先级的任务运行了,内核调度器总是将OS_LOWEST_PRIO在就绪表中相应字节的相应位置1。OSRdyGrp和OSRdyTbl[]的关系见图3.3,是按以下规则给出的: 当OSRdyTbl[0]中的任何一位是1时,OSRdyGrp的第0位置1,

        当OSRdyTbl[1]中的任何一位是1时,OSRdyGrp的第1位置1,

        当OSRdyTbl[2]中的任何一位是1时,OSRdyGrp的第2位置1,

        以些类推。。。。。

    任务就绪的掩码表:
           INT8U const OSMapTbl[]   = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};//定位优先级所用

    任务优先级的低三位用于确定任务在总就绪表OSRdyTbl[]中的所在位。接下去的[5:3]三位用于确定是在OSRdyTbl[] 数组的第几个元素。 OSMapTbl[]是在ROM中的(见文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]数组的元素下标在0到7之间,见表3.1

    IndexBit Mask (Binary)
    000000001
    100000010
    200000100
    300001000
    400010000
    500100000
    601000000
    710000000

         
     

    程序清单3.5中的代码用于将任务放入就绪表。Prio是任务的优先级。
     
    程序清单 L3.5 使任务进入就绪态 (这两行代码简直是神来之笔啊!!!)

    这行代码功能是找到组, 把组上的值置为1
    不妨假设prio的值为13, 即优先级为13. prio>>3 右移3位后值为1, 可以查表T3.1找出 OSMapTbl[1] 的值为 0000 0010. 再用 0000 0010 和 OSRdyGrp 进行异或运算:
    OSRdyGrp |= OSMapTbl[prio >> 3];   //先根据高3位[5:3]找到所在的组
    OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; //再由低3位把该组的具体某一位置1
     

    如果一个任务被删除了,则用程序清单3.6中的代码做求反处理。

    程序清单 L3.6 从就绪表中删除一个任务

     

    if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0)

        OSRdyGrp &= ~OSMapTbl[prio >> 3];

     

    以上代码将就绪任务表数组OSRdyTbl[]中相应元素的相应位清零,而对于OSRdyGrp,只有当被删除任务所在任务组中全组任务一个都没有进入就绪态时,才将相应位清零。也就是说OSRdyTbl[prio>>3]所有的位都是零时,OSRdyGrp的相应位才清零。为了找到那个进入就绪态的优先级最高的任务,并不需要从OSRdyTbl[0]开始扫描整个就绪任务表,只需要查另外一张表,即优先级判定表 OSUnMapTbl ([256])(见文件OS_CORE.C)。OSRdyTbl[]中每个字节的8位代表这一组的8个任务哪些进入就绪态了,低位的优先级高于高位。利用这个字节为下标来查OSUnMapTbl这张表,返回的字节就是该组任务中就绪态任务中优先级最高的那个任务所在的位置(即该字节的中最低位1的所在位)。这个返回值在0到7之间。确定进入就绪态的优先级最高的任务是用以下代码完成的。

    找出进入就绪态的优先级最高的任务 

    y    = OSUnMapTbl[OSRdyGrp];

    x    = OSUnMapTbl[OSRdyTbl[y]];

    prio = (y << 3) + x;

      优先级查询表:
    INT8U const OSUnMapTbl[] = {
        0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
        4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
    };//定位最高优先级用(即查找一个字节的最低位1所在的位置)

         例如,如果OSRdyGrp的值为二进制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是3,它相应于OSRdyGrp中的第3 位bit3,这里假设最右边的一位是第0位bit0。类似地,如果OSRdyTbl[3]的值是二进制11100100,则OSUnMapTbl [OSRdyTbc[3]]的值是2,即第2位。于是任务的优先级Prio就等于26(3*8+2)。利用这个优先级的值。查任务控制块优先级表 OSTCBPrioTbl[],得到指向相应任务的任务控制块OS_TCB的工作就完成了。

    五. 任务状态:

        uCOS II主要有五种任务状态,睡眠态就是挂起态,阻塞态和延时态这里统一为等待状态。增加了一个被中断状态。UC/OS-Ⅱ总是建立一个空闲任务,这个任务在没有其它任务进入就绪态时投入运行。这个空闲任务[OSTaskIdle()]永远设为最低优先级空闲任务OSTaskIdle()什么也不做,只是在不停地给一个32位的名叫OSIdleCtr的计数器加1,统计任务使用这个计数器以确定现行应用软件实际消耗的CPU时间。空闲任务不可能被应用软件删除。

        睡眠态(DORMANT)指任务驻留在程序空间之中,还没有交给μC/OS-Ⅱ管理,把任务交给μC/OS-Ⅱ是通过调用下述两个函数之一:OSTaskCreate()或OSTaskCreateExt()。当任务一旦建立,这个任务就进入就绪态准备运行。任务的建立可以是在多任务运行开始之前,也可以是动态地被一个运行着的任务建立。如果一个任务是被另一个任务建立的,而这个任务的优先级高于建立它的那个任务,则这个刚刚建立的任务将立即得到CPU的控制权。一个任务可以通过调用OSTaskDel()返回到睡眠态,或通过调用该函数让另一个任务进入睡眠态。

    调用OSStart()可以启动多任务。OSStart()函数运行进入就绪态的优先级最高的任务。就绪的任务只有当所有优先级高于这个任务的任务转为等待状态或者是被删除了,才能进入运行态。

      正在运行的任务可以通过调用两个函数之一将自身延迟一段时间,这两个函数是OSTimeDly()或OSTimeDlyHMSM()。这个任务于是进入等待状态,等待这段时间过去,下一个优先级最高的、并进入了就绪态的任务立刻被赋予了CPU的控制权。等待的时间过去以后,系统服务函数 OSTimeTick()使延迟了的任务进入就绪态(见3.10节,时钟节拍)。

     正在运行的任务期待某一事件的发生时也要等待,手段是调用以下 3个函数之一:OSSemPend(),OSMboxPend(),或OSQPend()。调用后任务进入了等待状态(WAITING)。当任务因等待事件被挂起(Pend),下一个优先级最高的任务立即得到了CPU的控制权。当事件发生了,被挂起的任务进入就绪态。事件发生的报告可能来自另一个任务,也可能来自中断服务子程序。

    正在运行的任务是可以被中断的,除非该任务将中断关了,或者μC/OS-Ⅱ将中断关了。被中断了的任务就进入了中断服务态(ISR)。响应中断时,正在执行的任务被挂起,中断服务子程序控制了CPU的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态。在这种情况下,从中断服务子程序返回之前,μC/OS-Ⅱ要判定,被中断的任务是否还是就绪态任务中优先级最高的。如果中断服务子程序使一个优先级更高的任务进入了就绪态,则新进入就绪态的这个优先级更高的任务将得以运行,否则原来被中断了的任务才能继续运行。

     当所有的任务都在等待事件发生或等待延迟时间结束,μC/OS-Ⅱ执行空闲任务(idle task),执行OSTaskIdle()函数。

    六. 任务切换:

        Context Switch  在有的书中翻译成上下文切换,实际含义是任务切换,或CPU寄存器内容切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态(Context),即CPU寄存器中的全部内容。这些内容保存在任务的当前状况保存区(Task’s Context Storage area),也就是任务自己的栈区之中。(见图2.2)。入栈工作完成以后,就是把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行。这个过程叫做任务切换。任务切换过程增加了应用程序的额外负荷。CPU的内部寄存器越多,额外负荷就越重。做任务切换所需要的时间取决于 CPU有多少寄存器要入栈。实时内核的性能不应该以每秒钟能做多少次任务切换来评价。

    七. 任务调度分析:

        uCOS II提供最简单的实时内核任务调度,算法简单,因此也只支持优先级抢占任务调度,不支持时间片轮训调度算法,不支持优先级逆转。

        uCOS II总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的,这个函数将在以后描述。

    uCOS II任务调度所花的时间是常数,与应用程序中建立的任务数无关

        为实现任务切换,OSTCBHighRdy必须指向优先级最高的那个任务控制块OS_TCB,这是通过将以 OSPrioHighRdy为下标的OSTCBPrioTbl[]数组中的那个元素赋给OSTCBHighRdy来实现的[L3.8(4)]。最后宏调用 OS_TASK_SW()来完成实际上的任务切换[L3.8(6)]。

         任务切换很简单,由以下两步完成,将被挂起任务的微处理器寄存器推入堆栈,然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。在uCOS II中,就绪任务的栈结构总是看起来跟刚刚发生过中断一样,所有微处理器的寄存器都保存在栈中。换句话说,μC/OS-Ⅱ运行就绪态的任务所要做的一切,只是恢复所有的CPU寄存器并运行中断返回指令。为了做任务切换,运行 OS_TASK_SW(),人为模仿了一次中断。多数微处理器有软中断指令或者陷阱指令TRAP来实现上述操作。中断服务子程序或陷阱处理(Trap hardler),也称作事故处理(exception handler),必须提供中断向量给汇编语言函数OSCtxSw()。 OSCtxSw()除了需要OS_TCBHighRdy指向即将被挂起的任务,还需要让当前任务控制块OSTCBCur指向即将被挂起的任务。

         OSSched ()的所有代码都属临界段代码。在寻找进入就绪态的优先级最高的任务过程中,为防止中断服务子程序把一个或几个任务的就绪位置位,中断是被关掉的。为缩短切换时间,OSSched()全部代码都可以用汇编语言写。为增加可读性,可移植性和将汇编语言代码最少化,OSSched()是用C写的。

    任务切换的相关函数:与CPU体系相关,汇编完成。

    1. OSStartHighRdy() 执行优先级最高的任务

    2. OSCtxSw()     完成任务的上下文切换

    3. OSIntCtxSw()  中断后的上下文切换

    4. OSTickISR()   中断服务程序启动

    八. uCOS II的初始化:

        OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级总是设成最低。

        这两个任务的任务控制块(OS_TCBs)是用双向链表链接在一起的。OSTCBList指向这个链表的起始处。当建立一个任务时,这个任务总是被放在这个链表的起始处。换句话说,OSTCBList总是指向最后建立的那个任务。链的终点指向空字符NULL(也就是零)。

        因为这两个任务都处在就绪态,在就绪任务表OSRdyTbl[]中的相应位是设为1的。还有,因为这两个任务的相应位是在OSRdyTbl[]的同一行上,即属同一组,故OSRdyGrp中只有1位是设为1的。 

        uCOS II还初始化了4个空数据结构缓冲区,如图F3.8所示。每个缓冲区都是单向链表,允许uCOS II从缓冲区中迅速得到或释放一个缓冲区中的元素。控制块OS_TCB的数目也就自动确定了。当然,包括足够的任务控制块分配给统计任务和空闲任务。

    附:调度机制

    进程调度是计算机的灵魂。在实时系统里,要使重要紧急的进程一经唤醒使被优先调度运行,系统就必须有基于进程优先级的实时调度策略。通过深入考察和对比μC/OS-II对实时调度算法的实现,可以深刻理解实时操作系统。
         如果说CPU是计算机系统的心脏,那么进程调度就是计算机系统的灵魂,因为它决定了如何使用CPU。例如,Linux是一个多任务操作系统,它的理想状况是保持CPU有效运行。如果某个正在运行的进程转入等待系统资源,操作系统就调度其他进程运行,从而保证CPU的最大利用率。如何使系统能够保证较短的响应时间和较高的吞吐量,使得多个进程竞争CPU时保持公平、高效,是通用操作系统所追求的目标。但对于实时操作系统而言,它的调度算法是基于POSIX规定的基于事件驱动优先级的调度算法,为了及时响应高优先级进程,它宁愿牺牲整体效率。     
        调度的实现可以分为2步来完成:
             ①何时启动调度,即解决调度启动时机的问题;
             ②怎么调度,按优先级调度就是要找到系统当前优先级最高的进程,然后进行上下文切换。      

       在实时系统中,只有当就绪进程集合发生变动时才有调度的需要,而就绪进程集合的变动只可能发生在几种情况下:
             ①运行中的进程受阻或自动放弃CPU;
             ②系统中新建了进程;
             ③运行中的进程“自杀”或“被杀”;
             ④运行中的进程唤醒了某个线程;
             ⑤中断服务子程序结束时唤醒了其他进程
    。      

       理想情况下,实时系统在有高优先级的进程转入就绪态时,就应该立即启动调度程序,响应高优先级进程。但实际上却存在着不可调度的时隙,称为不可调度窗口:
             ①正在进行进程切换,不能进行调度;
             ②中断响应期间,不能进行调度;
             ③进入临界区,不能进行调度;
             ④DMA期间CPU已被挂起,不可能进行调度。 在实时系统里,必须努力缩小不可调度窗口。在调度启动的时机上,所有的实时操作系统基本一致。
     

    转自:http://blog.163.com/hancker_31/blog/static/3558736120112216594592/

    展开全文
  • linux 任务调度机制

    千次阅读 2013-09-06 01:25:39
    linux 任务调度机制    Linux任务调度的时机  linux进程的调度时机大致分为两种情况: 一种是进程自愿调度;另一种是发生强制性调度。 首先,自愿的调度随时都可以进行。在内核空间中,进程...
    
    
    

     

    Linux任务调度的时机

               linux进程的调度时机大致分为两种情况: 一种是进程自愿调度;另一种是发生强制性调度。 首先,自愿的调度随时都可以进行。在内核空间中,进程可以通过schedule()启动一次调度;在用户空间中,可以通过系统调用pause()达到同样的目的。如果要为自愿的暂停行为加上时间限制,在内核中使用schedule_time(),而在用户空间则使用nanosleep()系统调用。

    linux中,强制性的调度发生在每次从系统调用返回的前夕,以及每次中断或异常处理返回用户空间的前夕。应注意的是,从内核态返回到用户态是进程调度发生的必要条件,而不是充分条件,还要取决于当进程task_struct结构中的need_resched是否为1

    从进程调度的时机可以看出,内核的调度方式为“有条件的剥夺方式”。当进程在用户空间运行,不管自愿不自愿,一旦有必要比如时间片用完,内核就可以暂时剥夺其运行而调度其他进程运行。而进程一旦进入内核空间,即进入核心态时,尽管知道应该要调度了,但实际上却不会发生,一直要到该进程返回到用户空间前夕才能剥夺其运行。

    Linux任务调度策略

               Linux支持SCHED_FIFOSCHED_RRSCHED_OTHER的调度策略。

               linux用函数goodness()统一计算进程(包括普通进程和实时进程)的优先级权值,该权值衡量一个处于可运行状态的进程值得运行的程度,权值越大,进程优先级越高。 每个进程的task_struct结构中,与goodness()计算权值相关的域有以下四项:policynice(2.2版内核该项为priority)counterrt_priority。其中,policy是进程的调度策略,其可用来区分实时进程和普通进程,实时进程优先于普通进程运行。nice从最初的UNIX沿用而来,表示进程的静态负向优先级,其取值范围为19~-20,以-20优先级最高。counter表示进程剩余的时间片计数值,由于counter在计算goodness()时起重要作用,因此,counter也可以看作是进程的动态优先级。rt_priority是实时进程特有的,表示实时优先级。

               首先,linux根据调度策略policy从整体上区分实时进程和普通进程。对于policySCHED_OTHER的普通进程,linux采用动态优先级调,其优先级权值取决于(20-nice)和进程当前的剩余时间片计数counter之和。进程创建时,子进程继承父进程的nice值,而父进程的counter值则被分为二半,子进程和父进程各得一半。时间片计数器每次清零后由(20-nice)经过换算重新赋值。字面上看,nice是“优先级”、counter是“计数器”的意思,然而实际上,它们表达的是同个意思:nice决定了分配给该进程的时间片计数,nice优先级越高的进程分到的时间片越长,用户通过系统调用nice()或setpriority()改变进程静态优先级nice值的同时,也改变了该进程的时间片长度;counter表示该进程剩余的时间片计数值,而nicecounter综合起来又决定进程可运行的优先级权值。在进程运行过程中,counter不断减少,而nice保持相对不变;当一个普通进程的时间片用完以后,并不马上根据nicecounter进行重新赋值,只有所有处于可运行状态的普通进程的时间片都用完了以后(counter等于0),才根据nicecounter重新赋值,这个普通进程才有了再次被调度的机会。这说明,普通进程运行过程中,counter的减小给了其它进程得以运行的机会,直至counter减为0时才完全放弃对CPU的使用,这就相当于优先级在动态变化,所以称之为动态优先调度。

                      对于实时进程,linux采用了两种调度策略,即SCHED_FIFO(先来先服务调度)SCHED_RR(时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个实时进程是否应该运行,采用了一个比较固定的标准,即参考rt_priority的值。用函数goodness()计算进程的优先级权值时,对实时进程是在1000的基础上加上rt_priority的值,而非实时进程的动态优先级综合起来的调度权值始终在以下,所以goodness()的优先级权值计算方法确保实时进程的调度权值始终比所有的非实时进程都要大,这就保证了实时进程的优先运行。实时进程的counternice都与其优先级权值无关,这和普通进程是有区别的,实时进程task_struct中的counternice只与SCHED_RR调度策略进程的时间片计数相关;而对于SCHED_FIFO调度策略的实时进程没有调度的参考意义。
    展开全文
  • linux任务调度机制

    千次阅读 2017-10-30 13:07:02
    4.调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载...

    一句话总结:普通进程采用优先级调度,实时进程采用FIFO和时间片轮转。还有其他实时调度算法,如最早截止优先算法


    下面摘自于:http://blog.csdn.net/caoyan_12727/article/details/52346729

    作业调度策略:

    进程调度在近几个版本中都进行了重要的修改。我们先了解一下进程调度的原理:

    (1)进程类型
    在linux调度算法中,将进程分为两种类型,即:I/O消耗型和CPU消耗型。例如文本处理程序与正在执行的Make的程序。文本处理程序大部份时间都在等待I/O设备的输入,而make程序大部份时间都在CPU的处理上。因此为了提高响应速度,I/O消耗程序应该有较高的优先级,才能提高它的交互性。相反的,Make程序相比之下就不那么重要了,只要它能处理完就行了。因此,基于这样的原理,linux有一套交互程序的判断机制。在task_struct结构中新增了一个成员:sleep_avg此值初始值为100。进程在CPU上执行时,此值减少。当进程在等待时,此值增加。最后,在调度的时候。根据sleep_avg的值重新计算优先级。
    (2)进程优先级
    正如我们在上面所说的:交互性强的需要高优先级,交互性弱的需要低优先级。在linux系统中,有两种优先级:普通优先级和实时优先级。我们在这里主要分析的是普通优先级,实时优先级部份可自行了解。
    (3)运行时间片
    进程的时间片是指进程在抢占前可以持续运行的时间。在linux中,时间片长短可根据优先级来调整。进程不一定要一次运行完所有的时间片。可以在运时的中途被切换出去。
    (4)进程抢占
    当一个进程被设为TASK_RUNING状态时,它会判断它的优先级是否高于正在运行的进程,如果是,则设置调度标志位,调用schedule()执行进程的调度。当一个进程的时间片为0时,也会执行进程抢占。
     调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选择进程的依据是什么呢?在每个进程的task_struct结构中有以下四 项:policy、priority、counter、rt_priority。这四项就是调度程序选择进程的依据.其中,policy是进程的调度策略,用来区分两种进程-实时和普通;priority是进程(实时和普通)的优先 级;counter 是进程剩余的时间片,它的大小完全由priority决定;rt_priority是实时优先级,这是实时进程所特有的,用于实时进程间的选择。 

    首先,Linux 根据policy从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度是不同的,它们两者之间,实时进程应该先于普通进程而运行,然后,对于同一类型的不同进程,采用不同的标准来选择进程: 

    policy的取值会有以下可能:

    SCHED_OTHER 分时调度策略,(默认的)
    SCHED_FIFO实时调度策略,先到先服务
    SCHED_RR实时调度策略,时间片轮转 实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice和counter值决定权值,nice越小,counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。 
    SHCED_RR和SCHED_FIFO的不同:当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。  
    SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有 更高优先级任务到达或自己放弃 。 
    如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。
    相同点:
    RR和FIFO都只用于实时任务。
    创建时优先级大于0(1-99)。
    按照可抢占优先级调度算法进行。
    就绪态的实时任务立即抢占非实时任务。


    对于普通进程,Linux采用动态优先调度,选择进程的依据就是进程counter的大小。进程创建时,优先级priority被赋一个初值,一般为 0~70之间的数字,这个数字同时也是计数器counter的初值,就是说进程创建时两者是相等的。字面上看,priority是"优先级"、 counter是"计数器"的意思,然而实际上,它们表达的是同一个意思-进程的"时间片"。Priority代表分配给该进程的时间片,counter 表示该进程剩余的时间片。在进程运行过程中,counter不断减少,而priority保持不变,以便在counter变为0的时候(该进程用完了所分 配的时间片)对counter重新赋值。当一个普通进程的时间片用完以后,并不马上用priority对counter进行赋值,只有所有处于可运行状态 的普通进程的时间片(p->counter==0)都用完了以后,才用priority对counter重新赋值,这个普通进程才有了再次被调度的 机会。这说明,普通进程运行过程中,counter的减小给了其它进程得以运行的机会,直至counter减为0时才完全放弃对CPU的使用,这就相对于 优先级在动态变化,所以称之为动态优先调度。至于时间片这个概念,和其他不同操作系统一样的,Linux的时间单位也是"时钟滴答",只是不同操作系统对 一个时钟滴答的定义不同而已(Linux为10ms)。进程的时间片就是指多少个时钟滴答,比如,若priority为20,则分配给该进程的时间片就为 20个时钟滴答,也就是20*10ms=200ms。Linux中某个进程的调度策略(policy)、优先级(priority)等可以作为参数由用户 自己决定,具有相当的灵活性。内核创建新进程时分配给进程的时间片缺省为200ms(更准确的,应为210ms),用户可以通过系统调用改变它。 

    对于实时进程,Linux采用了两种调度策略,即FIFO(先来先服务调度)和RR(时间片轮转调度)。因为实时进程具有一定程度的紧迫性,所以衡量一个 实时进程是否应该运行,Linux采用了一个比较固定的标准。实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标 准。实时进程的counter只是用来表示该进程的剩余时间片,并不作为衡量它是否值得运行的标准,这和普通进程是有区别的。上面已经看到,每个进程有两 个优先级(动态优先级和实时优先级),实时优先级就是用来衡量实时进程是否值得运行的。 

    Linux根据policy的值将进程总体上分为实时进程和普通进程,提供了三种调度算法:一种传统的Unix调度程序和两个由POSIX.1b(原名为 POSIX.4)操作系统标准所规定的"实时"调度程序。但这种实时只是软实时,不满足诸如中断等待时间等硬实时要求,只是保证了当实时进程需要时一定只 把CPU分配给实时进程。 


    非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级是一些简单的整数,为了决定应该允许哪一个进程使用CPU的资源,用优先级代表相对权值-优先级越高,它得到CPU时间的机会也就越大。 

      (1) 静态优先级(priority)-不随时间而改变,只能由用户进行修改。它指明了在被迫和其他进程竞争CPU之前,该进程所应该被允许的时间片的最大值(但很可能的,在该时间片耗尽之前,进程就被迫交出了CPU)。 
      (2)动态优先级(counter)-只要进程拥有CPU,它就随着时间不断减小;当它小于0时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。 
      (3)实时优先级(rt_priority)-指明这个进程自动把CPU交给哪一个其他进程;较高权值的进程总是优先于较低权值的进程。如果一个进程不是实时进程,其优先级就是0,所以实时进程总是优先于非实时进程的(但实际上,实时进程也会主动放弃CPU)。 

    当所有任务都采用FIFO调度策略时(SCHED_FIFO): 
    1.创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。 
    2.如果没有等待资源,则将该任务加入到就绪队列中。 
    3.调度程序遍历就绪队列,根据实时优先级计算调度权值,选择权值最高的任务使用cpu, 该FIFO任务将一直占有cpu直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。 
    4.调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。 
    5.如果当前任务因等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,此时重复第3步。 


    当所有任务都采用RR调度策略(SCHED_RR)时: 
    1.创建任务时指定调度参数为RR, 并设置任务的实时优先级和nice值(nice值将会转换为该任务的时间片的长度)。 
    2.如果没有等待资源,则将该任务加入到就绪队列中。 
    3.调度程序遍历就绪队列,根据实时优先级计算调度权值,选择权值最高的任务使用cpu。 
    4. 如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放入就绪队列的末尾 。重复步骤3。 
    5.当前任务由于等待资源而主动退出cpu,则其加入等待队列中。重复步骤3。 


    系统中既有分时调度,又有时间片轮转调度和先进先出调度: 
    1.RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。 
    2. 当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程 。 
    3. RR进程和FIFO进程都采

    作业调度算法:
    (1)先来先服务算法
    (2)段作业优先调度算法
    (3)优先级调度算法
    (4)时间片轮转调度算法
    (5)最高响应比优先调度算法
    响应比=周转时间/作业执行时间=(作业执行时间+作业等待时间)/作业执行时间=1+作业等待时间/作业执行时间;
    作业周转时间=作业完成时间-作业到达时间
    (6)多级反馈队列调度算法
    1、进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
    2、首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
    3、对于同一个队列中的各个进程,按照时间片轮转法调度。比如Q1队列的时间片为N,那么Q1中的作业在经历了N个时间片后若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
    4、在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(抢占式)。
    (7)实时调度算法
    A、最早截止时间优先调度算法
    B、最低松弛度优先调度算法
    种算法是根据任务紧急的程度,来确定任务的优先级。比如说,一个任务在200ms时必须完成而它本身运行需要100ms,所以此任务就必须在100ms之前调度执行,此任务的松弛度就是100ms。在实现此算法时需要系统中有一个按松弛度排序的实时任务就绪队列,松弛度最低的任务排在最烈的最前面,调度程序总是选择就粗队列中的首任务执行!(可理解为最早额定开始)

    版权声明:本文为博主原创文章,未经博主允许不得转载。

    展开全文
  • zigbee 任务调度机制

    千次阅读 2013-05-13 22:30:00
    osal采用轮询任务调度队列(任务链表),通过两个函数:调度程序主循环函数和设置事件发生标志函数。 2、时间管理:通过为事件设置超时等待时间,一旦等待时间结束,便为对应任务设置事件发生标志,
  • 全面解析JAVA中的任务调度机制

    千次阅读 2015-10-28 21:31:19
    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。...java.util.Timer是一种最简单的任务调度机制,使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设
  • contiki任务调度机制分析

    千次阅读 2014-11-26 14:43:25
    contiki任务调度机制分析 创建时间:2014-11-20 21:34   修改时间:2014-11-25 22:30   【目录Index】 事件进程的数据结构初始化中断的实现 systick的实现中断服务程序 任务的调度 【博客正文...
  • Windows的任务调度机制

    千次阅读 2018-02-23 17:08:41
    Windows的任务调度主要以线程为单位进行,线程拥有33个优先级,数值为0~32,其中0为最低优先级,32为最高优先级,最高和最低优先级均保留给系统使用。用户可以使用的优先级为1~31。   Windows按照优先级高低顺序来...
  • Linux任务调度机制(转)

    千次阅读 2019-04-09 19:19:45
    原文链接:http://liubigbin.github.io/2016/03/21/Linux任务调度机制/ 目录 基本概念 上下文切换 选择算法 Linux 调度器将进程分为三类 交互式进程 批处理进程 实时进程 调度时机: 1:主动式调度(自愿...
  • 第四节: VxWorks任务调度机制 在操作系统中,任务调度存在两种方式:基于优先级调度和基于时间片调度。嵌入式系统中任务调度一般都是基于优先级的调度方式,VxWorks也就是传说中的抢占式调度。有没有方法可以关闭...
  • Linux的任务调度机制

    万次阅读 2016-04-29 11:07:20
    通用Linux系统支持实时和非实时两种进程,实时进程相...在调度算法的实现上,Linux中的每个任务有四个与调度相关的参数,它们是rt_priority、policy、priority(nice)、counter。调度程序根据这四个参数进行进程调度
  • hadoop的任务调度机制

    千次阅读 2013-06-09 18:11:27
    随着MapReduce的流行,其开源实现Hadoop也变得越来越受推崇。在Hadoop系统中,有一个组件非常重要,那就是...在Hadoop中,调度器是一个可插拔的模块,用户可以根据自己的实际应用要求设计调度器。Hadoop中常见的调度
  • 详解UCOS中的任务调度机制

    千次阅读 2014-04-22 14:11:50
    一个操作系统内核提供的最核心的功能就是任务调度机制,操作系统的内核调度机制有大体有两种,一种是时间片轮番调度,就是将一个系统周期分为好几段,第一段时间执行第一个任务,第二段时间执行第二个任务.......
  • 本文简单介绍FairScheduler在进行任务调度时的方式,如果选择合适的Job,以及如何选择合适的Task。Hadoop有三种不同的任务调度策略(自带的FIFO,以及第三方的FairScheduler和CapacityScheduler),本文介绍的是Fair...
  • Spark内核详解 (5) | Spark的任务调度机制

    千次阅读 多人点赞 2020-08-30 09:07:46
      大家好,我是不温卜火,是一名计算机学院大数据专业大二的学生,昵称来源于成语—不温不火,本意是希望自己性情温和。作为一名互联网行业的小白,博...  本片博文为大家带来的是Spark的任务调度机制。 目录 .
  • ucos-ii的任务调度机制

    万次阅读 2010-12-06 22:59:00
    最近看了下ucos-ii的任务调度这块,有些收获,但还有困惑的地方,这里写下作为自己的总结。 1、在ucos-ii中,有这么几张表来管理任务。 A、OSTCBPrioTbl[],其结构为OS_TCB指针的数组,其元素个数为64, 每一个...
  • freertos内核走读2——task任务调度机制
  • freertos 任务调度机制,任务的创建删除delay,调度器的开启终止暂停和恢复等。
  • 嵌入式操作系统学习(3)FreeRTOS的任务调度机制

    万次阅读 多人点赞 2018-07-09 16:29:15
    1.任务状态 ...处于非运行态的任务,它的所有寄存器状态都保存在自己的任务堆栈中,当调度器将其恢复到运行态时,会从上一次离开运行态时正准备执行的那条指令开始执行。 如下图所示,从整体上操作系统调...
  • hadoop的备份任务调度机制

    千次阅读 2013-03-29 17:19:21
    随着MapReduce的流行,其开源实现Hadoop也变得越来越受推崇。在Hadoop系统中,有一个组件非常重要,那就是...在Hadoop中,调度器是一个可插拔的模块,用户可以根据自己的实际应用要求设计调度器。Hadoop中常见的调度
  • task notify机制
  • Linux与VxWorks任务调度机制分析

    千次阅读 2013-07-04 09:42:27
    本文通过分析与比较Linux和VxWorks任务(进程)调度机制的异同,有助于理解实时内核和通用分时内核在任务调度机制实现层次上的差异,并基于它们更有针对性地开发相应的应用,同时对基于通用操作系统Linux的一些实时化...
  • 本文分析任务调度机制源码 详见:../kernel/base/sched/sched_sq/los_sched.c 建议先阅读 阅读本文之前建议先读 鸿蒙内核源码分析(Task/线程管理篇) 鸿蒙内核源码分析(进程管理篇) 鸿蒙内核源码分析(调度队列篇)...
  • Spark内核之任务调度机制及源码分析

    千次阅读 2020-09-20 18:33:46
    http://blog.csdn.net/qq_16146103/article/details/108095536
  • 什么是真正的实时操作系统 做嵌入式系统开发有一段时间了,做过用于手机平台的嵌入式Linux,也接触过用于交换机、媒体网关平台的VxWorks。实际应用后回过头来看理论,才发现自己理解的肤浅,也发现CSDN上好多同学...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 177,625
精华内容 71,050
关键字:

任务调度机制是什么