四 计划任务(TaskScheduler)深入探讨

我们且把三种任务命名为:socket handler,event handler,delay task

这三种任务的特点是,前两个加入执行队列后会一直存在,而delay task在执行完一次后会立即弃掉。

socket handler保存在队列BasicTaskScheduler0::HandlerSet* fHandlers中;

event handler保存在数组BasicTaskScheduler0::TaskFunc * fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS]中;

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一样。

再看一下向任务调度对象添加三种任务的函数的样子:
delay task为:
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中就只有表明读的位被设置。

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

EventTriggerId BasicTaskScheduler0::createEventTrigger( TaskFunc* eventHandlerProc) { unsigned i = fLastUsedTriggerNum; EventTriggerId mask = fLastUsedTriggerMask; //在数组中寻找一个未使用的项,把eventHandlerProc分配到这一项。 do { i = (i + 1) % MAX_NUM_EVENT_TRIGGERS; mask >>= 1; if (mask == 0) mask = 0x80000000; if (fTriggeredEventHandlers[i] == NULL) { // This trigger number is free; use it: fTriggeredEventHandlers[i] = eventHandlerProc; fTriggeredEventClientDatas[i] = NULL; // sanity fLastUsedTriggerMask = mask; fLastUsedTriggerNum = i; return mask; //分配成功,返回值表面了第几项 } } while (i != fLastUsedTriggerNum);//表明在数组中循环一圈 //数组中的所有项都被占用,返回表明失败。 // All available event triggers are allocated; return 0 instead: return 0; }
可以看到最多添加32个事件,且添加事件时没有传入clientData参数。这个参数在触发事件时传入,见以下函数:
void BasicTaskScheduler0::triggerEvent(EventTriggerId eventTriggerId,void* clientData) { // First, record the "clientData": if (eventTriggerId == fLastUsedTriggerMask) { // common-case optimization:直接保存下clientData fTriggeredEventClientDatas[fLastUsedTriggerNum] = clientData; } else { //从头到尾查找eventTriggerId对应的项,保存下clientData EventTriggerId mask = 0x80000000; for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) { if ((eventTriggerId & mask) != 0) { fTriggeredEventClientDatas[i] = clientData; fLastUsedTriggerMask = mask; fLastUsedTriggerNum = i; } mask >>= 1; } } // Then, note this event as being ready to be handled. // (Note that because this function (unlike others in the library) // can be called from an external thread, we do this last, to // reduce the risk of a race condition.) //利用fTriggersAwaitingHandling以bit mask的方式记录需要响应的事件handler们。 fTriggersAwaitingHandling |= eventTriggerId; }看,clientData被传入了,这表明clientData在每次触发事件时是可以变的。

此时再回去看SingleStep()是不是更明了了?

delay task添加时,需要传入task延迟等待的微秒(百万分之一秒)数(第一个参数),这个弱智也可以理解吧?嘿嘿。分析一下介个函数:

TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,TaskFunc* proc, void* clientData) { if (microseconds < 0) microseconds = 0; //DelayInterval 是表示时间差的结构 DelayInterval timeToDelay((long) (microseconds / 1000000),(long) (microseconds % 1000000)); //创建delayQueue中的一项 AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData,timeToDelay); //加入DelayQueue fDelayQueue.addEntry(alarmHandler); //返回delay task的唯一标志 return (void*) (alarmHandler->token()); } delay task的执行都在函数fDelayQueue.handleAlarm()中,handleAlarm()在类DelayQueue中实现。看一下handleAlarm(): void DelayQueue::handleAlarm() { //如果第一个任务的执行时间未到,则同步一下(重新计算各任务的等待时间)。 if (head()->fDeltaTimeRemaining != DELAY_ZERO) synchronize(); //如果第一个任务的执行时间到了,则执行第一个,并把它从队列中删掉。 if (head()->fDeltaTimeRemaining == DELAY_ZERO) { // This event is due to be handled: DelayQueueEntry* toRemove = head(); removeEntry(toRemove); // do this first, in case handler accesses queue //执行任务,执行完后会把这一项销毁。 toRemove->handleTimeout(); } }可能感觉奇怪,其它的任务队列都是先搜索第一个应该执行的项,然后再执行,这里干脆,直接执行第一个完事。那就说明第一个就是最应该执行的一个吧?也就是等待时间最短的一个吧?那么应该在添加任务时,将新任务跟据其等待时间插入到适当的位置而不是追加到尾巴上吧?猜得对不对还得看fDelayQueue.addEntry(alarmHandler)这个函数是怎么执行的。
void DelayQueue::addEntry(DelayQueueEntry* newEntry) { //重新计算各项的等待时间 synchronize(); //取得第一项 DelayQueueEntry* cur = head(); //从头至尾循环中将新项与各项的等待时间进行比较 while (newEntry->fDeltaTimeRemaining >= cur->fDeltaTimeRemaining) { //如果新项等待时间长于当前项的等待时间,则减掉当前项的等待时间。 //也就是后面的等待时几只是与前面项等待时间的差,这样省掉了记录插入时的时间的变量。 newEntry->fDeltaTimeRemaining -= cur->fDeltaTimeRemaining; //下一项 cur = cur->fNext; } //循环完毕,cur就是找到的应插它前面的项,那就插它前面吧 cur->fDeltaTimeRemaining -= newEntry->fDeltaTimeRemaining; // Add "newEntry" to the queue, just before "cur": newEntry->fNext = cur; newEntry->fPrev = cur->fPrev; cur->fPrev = newEntry->fPrev->fNext = newEntry; }有个问题,while循环中为什么没有判断是否到达最后一下的代码呢?难道肯定能找到大于新项的等待时间的项吗?是的!第一个加入项的等待时间是无穷大的,而且这一项永远存在于队列中。

live555学习笔记4相关推荐

  1. live555 学习笔记-建立RTSP连接的过程(RTSP服务器端)

    live555 学习笔记-建立RTSP连接的过程(RTSP服务器端) 监听 创建rtsp server,rtspserver的构造函数中,创建监听socket,添加到调度管理器BasicTaskSch ...

  2. live555学习笔记2-基础类

    二 基础类 讲几个重要的基础类: BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类.比如UsageEnvironment代表了整个系统运行 ...

  3. live555 学习笔记

    2019独角兽企业重金招聘Python工程师标准>>> 从程序的结构来看,live项目包括了四个基本库.程序入口类(在mediaServer中)和一些测试代码(在testProgs中 ...

  4. live555学习笔记-RTSPClient分析

    八 RTSPClient分析 有RTSPServer,当然就要有RTSPClient. 如果按照Server端的架构,想一下Client端各部分的组成可能是这样: 因为要连接RTSP server,所 ...

  5. live555学习笔记-RTP打包与发送

    RTP打包与发送 rtp传送开始于函数:MediaSink::startPlaying().想想也有道理,应是sink跟source要数据,所以从sink上调用startplaying(嘿嘿,相当于d ...

  6. live555学习笔记-RTSP服务运作

    RTSP服务运作 基础基本搞明白了,那么RTSP,RTP等这些协议又是如何利用这些基础机制运作的呢? 首先来看RTSP. RTSP首先需建立TCP侦听socket.可见于此函数: [cpp] view ...

  7. live555学习笔记【3】---RTSP服务器(一)

    Live555库是一个使用开放标准协议如RTP/RTCP.RTSP.SIP等实现多媒体流式传输的开源C 库集.这些函数库可以在Unix.Windows.QNX等操作系统下编译使用,基于此建立RTSP/ ...

  8. Live555学习笔记(一)—— live555概述

    IVE555是为流媒体提供解决方案的跨平台C++开源项目. 一.各库简要介绍 LIVE555下包含LiveMedia.UsageEnvironment.BasicUsageEnvironment.Gr ...

  9. live555学习笔记7-RTP打包与发送

    七 RTP打包与发送 rtp传送开始于函数:MediaSink::startPlaying().想想也有道理,应是sink跟source要数据,所以从sink上调用startplaying(嘿嘿,相当 ...

  10. live555学习笔记3-消息循环

    三 消息循环 看服端的主体:live555MediaServer.cpp中的main()函数,可见其创建一个RTSPServer类实例后,即进入一个函数env->taskScheduler(). ...

最新文章

  1. 10大类、142条数据源,中文NLP数据集线上搜索开放
  2. Sring boot学习笔记(三)-自带注解定时任务使用
  3. MATLAB中的fft后为何要用fftshift?
  4. Java-类加载内存分析
  5. 又真香了!到底是怎样的软件测试面试文档,拿到这么多大厂offer
  6. i5 13600KF参数 酷睿i53600KF什么水平i5 13600KF核显相当于什么显卡
  7. 天津滨海农商银行数据脱敏建设实践
  8. [微服务]API 路由管理--Gateway网关
  9. openwrt上透明AP的实现
  10. Visual Studio 2017卸载不干净
  11. Python+unittest+requests 接口自动化测试框架搭建 完整的框架搭建过程 实战
  12. 修改电量android,安卓手机端修改电池电量图标的教程
  13. pythonapp推荐_初学python编程,有哪些不错的软件值得一用?
  14. ata驱动框架及scsi请求处理流程
  15. tensorflow Variable already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUS
  16. Python学习:小数/浮点数(float)类型详解
  17. 推荐一款数据可视化分析工具
  18. Python实现美国费城Danny`s Wok中餐馆菜单分析
  19. 内外网的安全隔离技术实现
  20. 数学建模竞赛—基金投资组合优化及绩效评价问题

热门文章

  1. 彻底了解toString和valueOf区别
  2. 要点初见:从零开始进行妙算manifold、Jetson TK1环境配置
  3. ISP一键下载电路,上电瞬间引起单片机复位
  4. 谈谈陌陌争霸在数据库方面踩过的坑
  5. Kindeditor编辑器使用
  6. Tcl/Tk开发工具软件推荐
  7. 将Uity中的3D场景导入Laya并在Chrome浏览器中显示
  8. 在你的网站、浏览器中集成3D模型预览功能,使用开源项目Online3DViewer
  9. java动态分区分配算法,操作系统_动态分区分配算法课程设计_java版
  10. ROS学习笔记riki