計劃任務(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)
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)
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中就只有
表明讀的位被設置。
(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) 這個函數是怎麼執行的。
直接執行第一個完事。那就說明第一個就是最應該執行的一個吧?也就是等待時間最短的一
個吧?那麼應該在添加任務時,將新任務跟據其等待時間插入到適當的位置而不是追加到尾
巴上吧?猜得對不對還得看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循環中爲什麼沒有判斷是否到達最後一下的代碼呢?難道肯定能找到大於
新項的等待時間的項嗎?是的!第一個加入項的等待時間是無窮大的,而且這一項永遠存在
於隊列中。