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中就只有
表明讀的位被設置。
void BasicTaskScheduler
  ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {
  if (socketNum < 0) return;
  FD_CLR((unsigned)socketNum, &fReadSet);
  FD_CLR((unsigned)socketNum, &fWriteSet);
  FD_CLR((unsigned)socketNum, &fExceptionSet);
  if (conditionSet == 0) {
    fHandlers->clearHandler(socketNum);
    if (socketNum+1 == fMaxNumSockets) {
      --fMaxNumSockets;
    }
  } else {
    fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);
    if (socketNum+1 > fMaxNumSockets) {
      fMaxNumSockets = socketNum+1;
    }
    if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);
    if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);
    if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);
  }
}
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;

  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".  (Note that we allow "eventTriggerId" to be a combination of bits for multiple events.)
  EventTriggerId mask = 0x80000000;
  for (unsigned i = 0; i < MAX_NUM_EVENT_TRIGGERS; ++i) {
    if ((eventTriggerId&mask) != 0) {
      fTriggeredEventClientDatas[i] = clientData;
    }
    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 |= eventTriggerId;
}
看,clientData 被傳入了,這表明 clientData 在每次觸發事件時是可以變的。此時再回去看
SingleStep()是不是更明瞭了? 

delay task 添加時,需要傳入 task 延遲等待的微秒(百萬分之一秒)數( 第一個參數),這個
弱智也可以理解吧?嘿嘿。分析一下介個函數:
TaskToken BasicTaskScheduler0::scheduleDelayedTask(int64_t microseconds,
						 TaskFunc* proc,
						 void* clientData) {
  if (microseconds < 0) microseconds = 0;
  DelayInterval timeToDelay((long)(microseconds/1000000), (long)(microseconds%1000000));
  AlarmHandler* alarmHandler = new AlarmHandler(proc, clientData, timeToDelay);
  fDelayQueue.addEntry(alarmHandler);

  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循環中爲什麼沒有判斷是否到達最後一下的代碼呢?難道肯定能找到大於
新項的等待時間的項嗎?是的!第一個加入項的等待時間是無窮大的,而且這一項永遠存在
於隊列中。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章