live555學習筆記4-計劃任務(TaskScheduler)深入探討

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

 

轉自: http://blog.csdn.net/niu_gao/article/details/6910549

發佈了7 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章