zoukankan      html  css  js  c++  java
  • live555峰哥的私房菜(二)-----计划任务(TaskScheduler)探讨

    计划任务(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循环中为什么没有判断是否到达最后一下的代码呢?难道肯定能找到大于
    新项的等待时间的项吗?是的!第一个加入项的等待时间是无穷大的,而且这一项永远存在
    于队列中。
  • 相关阅读:
    js 把数组中每个元素的某个字段取出
    vue 实现单选/多选效果
    js常用的array方法
    js的split()和join()的用法
    HTML 转 PDF 之 wkhtmltopdf
    微信小程序api封装(promise)
    常用的正则表达式
    更改MySQL的存储目录
    CentOS 6.X 安装VirtualBox-5.1
    CentOS 6.X 安装VNC Server实现图形化访问
  • 原文地址:https://www.cnblogs.com/lidabo/p/9467203.html
Copyright © 2011-2022 走看看