计划任务(TaskScheduler)探讨

上一篇谈到SingleStep()函数会找到三种任务类型并执行之。
这三种任务是:
socket handler, event handler, delay task 。 
1、socket handler 保存在队列BasicTaskScheduler0::HandlerSet* fHandlers中;
2、event handler保存在数组BasicTaskScheduler0::TaskFunc * fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS] 中;
3、delay task 保存在队列BasicTaskScheduler0::DelayQueue fDelayQueue 中。 
下面看一下三种任务的执行函数的定义: 
socket handler 为 
typedef void BackgroundHandlerProc (void* clientData, int mask); 
event handler为 
typedef void TaskFunc(void* clientData); 
delay task  为 
typedef void TaskFunc(void* clientData);// 跟event handler一样。 
再看一下向任务调度对象添加三种任务的函数的样子: 
socket handler 为: 
void setBackgroundHandling(int socketNum, int conditionSet   ,BackgroundHandlerProc* 
handlerProc, void* clientData)
event handler为: 
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc) 
delay task 为: 
TaskToken scheduleDelayedTask(int64_t  microseconds, TaskFunc* proc,void* 
clientData)
 
socket handler 添加时为什么需要那些参数呢?socketNum 是需要的,因为要select socket
(socketNum 即是socket() 返回的那个socket 对象)。conditionSet 也是需要的,它用于
表明socket 在select 时查看哪种装态,是可读?可写?还是出错?proc 和clientData 这两
个参数就不必说了(真有不明白的吗?)。再看BackgroundHandlerProc 的参数,socketNum
不必解释,mask是什么呢?它正是对应着 conditionSet ,但它表明的是 select 之后的结果,
比如一个socket 可能需要检查其读/ 写状态,而当前只能读,不能写,那么mask中就只有
表明读的位被设置。
  1.  
    void BasicTaskScheduler
  2.  
    ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
  3.  
    if (socketNum < 0) return;
  4.  
    FD_CLR((unsigned)socketNum, &fReadSet);
  5.  
    FD_CLR((unsigned)socketNum, &fWriteSet);
  6.  
    FD_CLR((unsigned)socketNum, &fExceptionSet);
  7.  
    if (conditionSet == 0) {
  8.  
    fHandlers->clearHandler(socketNum);
  9.  
    if (socketNum+1 == fMaxNumSockets) {
  10.  
    --fMaxNumSockets;
  11.  
    }
  12.  
    } else {
  13.  
    fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
  14.  
    if (socketNum+1 > fMaxNumSockets) {
  15.  
    fMaxNumSockets = socketNum+1;
  16.  
    }
  17.  
    if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);
  18.  
    if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);
  19.  
    if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);
  20.  
    }
  21.  
    }

event handler是被存在数组中。数组大小固定,是32项,用 EventTriggerId 来表示数组中
的项,EventTriggerId 是一个32位整数,因为数组是32项,所以用EventTriggerId 中的
第n 位置1表明对应数组中的第n 项。成员变量fTriggersAwaitingHandling 也是
EventTriggerId 类型,它里面置 1 的那些位对应了数组中所有需要处理的项。这样做节省了
内存和计算,但降低了可读性,呵呵,而且也不够灵活,只能支持32项或64项,其它数
量不被支持。以下是函数体 

  1.  
    EventTriggerId BasicTaskScheduler0::createEventTrigger(TaskFunc* eventHandlerProc) {
  2.  
    unsigned i = fLastUsedTriggerNum;
  3.  
    EventTriggerId mask = fLastUsedTriggerMask;
  4.  
     
  5.  
    do {
  6.  
    i = (i+1)%MAX_NUM_EVENT_TRIGGERS;
  7.  
    mask >>= 1;
  8.  
    if (mask == 0) mask = 0x80000000;
  9.  
     
  10.  
    if (fTriggeredEventHandlers[i] == NULL) {
  11.  
    // This trigger number is free; use it:
  12.  
    fTriggeredEventHandlers[i] = eventHandlerProc;
  13.  
    fTriggeredEventClientDatas[i] = NULL; // sanity
  14.  
     
  15.  
    fLastUsedTriggerMask = mask;
  16.  
    fLastUsedTriggerNum = i;
  17.  
     
  18.  
    return mask;
  19.  
    }
  20.  
    } while (i != fLastUsedTriggerNum);
  21.  
     
  22.  
    // All available event triggers are allocated; return 0 instead:
  23.  
    return 0;
  24.  
    }

可以看到最多添加32个事件,且添加事件时没有传入 clientData 参数。这个参数在
触发事件时传入,见以下函数: 

  1.  
    void BasicTaskScheduler0::triggerEvent(EventTriggerId eventTriggerId, void* clientData) {
  2.  
    // First, record the "clientData". (Note that we allow "eventTriggerId" to be a combination of bits for multiple events.)
  3.  
    EventTriggerId mask = 0x80000000;
  4.  
    for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) {
  5.  
    if ((eventTriggerId&mask) != 0) {
  6.  
    fTriggeredEventClientDatas[i] = clientData;
  7.  
    }
  8.  
    mask >>= 1;
  9.  
    }
  10.  
     
  11.  
    // Then, note this event as being ready to be handled.
  12.  
    // (Note that because this function (unlike others in the library) can be called from an external thread, we do this last, to
  13.  
    // reduce the risk of a race condition.)
  14.  
    fTriggersAwaitingHandling |= eventTriggerId;
  15.  
    }

看,clientData 被传入了,这表明 clientData 在每次触发事件时是可以变的。此时再回去看
SingleStep()是不是更明了了? 

delay task 添加时,需要传入 task 延迟等待的微秒(百万分之一秒)数( 第一个参数),这个
弱智也可以理解吧?嘿嘿。分析一下介个函数:
  1.  
    TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,
  2.  
    TaskFunc* proc,
  3.  
    void* clientData) {
  4.  
    if (microseconds < 0) microseconds = 0;
  5.  
    DelayInterval timeToDelay((long)(microseconds/1000000), (long)(microseconds%1000000));
  6.  
    AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData, timeToDelay);
  7.  
    fDelayQueue.addEntry(alarmHandler);
  8.  
     
  9.  
    return (void*)(alarmHandler->token());
  10.  
    }

delay task的执行都在函数fDelayQueue.handleAlarm() 中,handleAlarm()在类
DelayQueue 中实现。看一下handleAlarm():   

  1.  
    void DelayQueue::handleAlarm() {
  2.  
    //如果第一个任务的执行时间未到,则同步一下(重新计算各任务的等待时间)。
  3.  
    if (head()->fDeltaTimeRemaining != DELAY_ZERO) synchronize();
  4.  
    //如果第一个任务的执行时间到了,则执行第一个,并把它从队列中删掉。
  5.  
    if (head()->fDeltaTimeRemaining == DELAY_ZERO) {
  6.  
    // This event is due to be handled:
  7.  
    DelayQueueEntry* toRemove = head();
  8.  
    removeEntry(toRemove); // do this first, in case handler accesses queue
  9.  
    //执行任务,执行完后会把这一项销毁。
  10.  
    toRemove->handleTimeout();
  11.  
    }
  12.  
    }
可能感觉奇怪,其它的任务队列都是先搜索第一个应该执行的项,然后再执行,这里干脆,
直接执行第一个完事。那就说明第一个就是最应该执行的一个吧?也就是等待时间最短的一
个吧?那么应该在添加任务时,将新任务跟据其等待时间插入到适当的位置而不是追加到尾
巴上吧?猜得对不对还得看fDelayQueue.addEntry(alarmHandler) 这个函数是怎么执行的。
  1.  
    void DelayQueue::addEntry(DelayQueueEntry* newEntry) {
  2.  
    // 重新计算各项的等待时间
  3.  
    synchronize();
  4.  
    // 取得第一项
  5.  
    DelayQueueEntry* cur = head();
  6.  
    // 从头至尾循环中将新项与各项的等待时间进行比较
  7.  
    while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) {
  8.  
    // 如果新项等待时间长于当前项的等待时间,则减掉当前项的等待时间。
  9.  
    newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining;
  10.  
    cur = cur->fNext;
  11.  
    }
  12.  
    //循环完毕,cur 就是找到的应插它前面的项,那就插它前面吧
  13.  
    cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining;
  14.  
     
  15.  
    // Add "newEntry" to the queue, just before "cur":
  16.  
    newEntry->fNext = cur;
  17.  
    newEntry->fPrev = cur->fPrev;
  18.  
    cur->fPrev = newEntry->fPrev->fNext = newEntry;
  19.  
    }

有个问题,while循环中为什么没有判断是否到达最后一下的代码呢?难道肯定能找到大于
新项的等待时间的项吗?是的!第一个加入项的等待时间是无穷大的,而且这一项永远存在
于队列中。

live555峰哥的私房菜(二)-----计划任务(TaskScheduler)探讨的更多相关文章

  1. 鸟哥Linux私房菜知识汇总8至9章

    一看最近<鸟哥Linux私房菜>. 这是一个基本的书,万丈高楼平地起,学. 这是我整理的一些知识点.尽管非常基础. 希望和大家共同交流. 第8章 Linux磁盘与文件系统管理 一.Linu ...

  2. C#私房菜[二][提供编程效率的技巧]

    AaronYang的C#私房菜[二][提供编程效率的技巧] 前言 我的文章简单易懂,能学到东西.因为复杂的东西,讲起来,好累.阅读者只是膜拜,学不到东西,就是没用的东西,好多文章都是看不下去.我写不出 ...

  3. 鸟哥Linux私房菜基础学习篇学习笔记3

    鸟哥Linux私房菜基础学习篇学习笔记3 第十二章 正则表达式与文件格式化处理: 正则表达式(Regular Expression) 是通过一些特殊字符的排列,用以查找.删除.替换一行或多行文字字符: ...

  4. [Linux]《鸟哥的私房菜》笔记 (缓慢更新)

    暂时不更新了..这几天一看起书来发现内容很多,这样写blog太慢,也没意义.所以现在是每天看书,在笔记本上记笔记,再配合着<操作系统>和 linux内核 加深理解.往后会以心得体会为主写一 ...

  5. linux: 鸟哥的私房菜

    鸟哥的私房菜 http://vbird.dic.ksu.edu.tw/linux_basic/0320bash.php

  6. 鸟哥Linux私房菜基础学习篇学习笔记2

    鸟哥Linux私房菜基础学习篇学习笔记2 第九章 文件与文件系统的压缩打包: Linux下的扩展名没有什么特殊的意义,仅为了方便记忆. 压缩文件的扩展名一般为: *.tar, *.tar.gz, *. ...

  7. 鸟哥Linux私房菜基础学习篇学习笔记1

    鸟哥Linux私房菜基础学习篇学习笔记1 第三章 主导分区(MBR),当系统在开机的时候会主动去读取这个区块的内容,必须对硬盘进行分区,这样硬盘才能被有效地使用. 所谓的分区只是针对64Bytes的分 ...

  8. 鸟哥linux私房菜第6章笔记

    鸟哥linux私房菜第6章笔记 文件权限 修改 chgrp [-R] groupname filename //修改文件所属组 chown [-R] ownername[:groupname] fil ...

  9. 学习鸟哥linux私房菜--安装centos5.6(u盘安装,中文乱码)

    题头为"学习鸟哥Linux私房菜"的内容,均为博主在看鸟哥的Linux私房菜第三版的学习经历收获.以下正文: 鸟哥第一部分讲Linux规则与安装,看到第四章正式开始讲实际安装,于是 ...

随机推荐

  1. 【转】visualSFM生成的bundle.rd.out文件的格式

    1.bundle.out 文件包含了一些经过估算得到的场景和相机几何信息.文件的格式如下: //---------------------------------------------------- ...

  2. 02 http,servlet,servletconfig,HttpServletRequest ,HttpServletResponse

    Http协议 协议:双方在交互.通讯的时候, 遵守的一种规范.规则.http协议:针对网络上的客户端 与 服务器端在执行http请求的时候,遵守的一种规范. 其实就是规定了客户端在访问服务器端的时候, ...

  3. if else的使用以及如何从键盘获取数值

    if-else的使用 顺序结构 顺序从上到下执行,中间没有判断和跳转 分支结构 根据条件,选择性地执行某段代码 有if-else和switch两种分支语句 循环结构 根据循环,重复性地执行某段代码 有 ...

  4. node学习笔记之io.sockets

    socket.get和socket.set函数已经失效,代码修改如下所示: 服务器端: var httpd = require('http').createServer(handler); var i ...

  5. 把oracle数据库恢复到某个时间点或者某个scn

    alter session set nls_date_format='yyyymmdd hh24:mi:ss'; select sysdate from dual; conn dbauser/1234 ...

  6. buckaroo 试用

    我系统是mac 所以选择的是mac 的版本,官方是支持跨平台的. 安装 mac 版本安装 wget https://github.com/LoopPerfect/buckaroo/releases/d ...

  7. quicklink 基本使用

    原理 使用可见性以及预取数据,同时充分利用浏览器的空闲时间,主要是解析href 以通过代码的选项指定需要加载的数据,当然其中 也添加了好多灵活的控制参数,方便我们使用,而且代码很小,压缩之后也就1kb ...

  8. python基础教程_学习笔记9:抽象

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/signjing/article/details/30745465 抽象 懒惰即美德. 抽象和结构 抽 ...

  9. 深入详解美团点评CAT跨语言服务监控(三)CAT客户端原理

    cat客户端部分核心类 message目录下面有消息相关的部分接口 internal目录包含主要的CAT客户端内部实现类: io目录包含建立服务端连接.重连.消息队列监听.上报等io实现类: spi目 ...

  10. 20165308 实验二 Java面向对象程序设计

    20165308 实验二 Java面向对象程序设计 实验二 Java面向对象程序设计 一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:张士洋 学号:20165308 指导教师:娄嘉鹏 ...