我們且把三種任務命名為: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項,其它數量不被支持。以下是函數體
[cpp] view plain copyEventTriggerId 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參數。這個參數在觸發事件時傳入,見以下函數:[cpp] view plain copyvoid 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延遲等待的微秒(百萬分之一秒)數(第一個參數),這個弱智也可以理解吧?嘿嘿。分析一下介個函數:
[cpp] view plain copyTaskToken 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)這個函數是怎么執行的。[cpp] view plain copyvoid 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循環中為什么沒有判斷是否到達最后一下的代碼呢?難道肯定能找到大于新項的等待時間的項嗎?是的!第一個加入項的等待時間是無窮大的,而且這一項永遠存在于隊列中。
|
新聞熱點
疑難解答