使用C語言模擬Node(二) ——事件循環

1. 同步和異步

舉個簡單的例子,同步就是你早上燒熱水的時候必須盯着,水開了以後纔可以上廁所,異步就是你在燒水的時候可以先去上廁所刷牙等,但是有些情況下你沒有辦法異步,就像你沒有辦法一邊燒水一邊喝熱水。由此可見同步在一些情況下體驗很糟糕,同樣異步在某些情況下又和同步無異。在高併發情境下,單線程同步執行阻塞情況會非常嚴重,後續請求將遲遲得不到響應。多線程異步是給每一個請求分配一個線程去執行,當請求數較小時可以達到並行,但是如果併發嚴重,就會導致大量的線程處於等待狀態,不僅浪費資源,頻繁切換線程也會對性能造成浪費。
js本身是單線程的,但是它維護了一個事件隊列,再啓動時會將隊列放在一個死循環中,每次取出隊列的第一個任務執行,直到隊列爲空,程序纔會終止。

2. 事件循環機制

nodejs是單線程的,這樣說其實不太恰當,準確來說nodejs是由一個主線程和若干服務線程組成的,對於開發者來說,只需要面對主線程,其它服務線程都是由node管理的,所以用起來就感覺像是單線程的。當node執行異步讀取一個文件的時候,主線程只需要告訴IO服務線程自己要讀取的文件然後就繼續執行事件隊列中其它的任務了,真正調用系統底層IO接口的其實是服務線程,服務線程讀取到結果時會將回調任務寫入任務隊列,等待主線程執行。

3. 定義任務隊列結構

  • 任務狀態
enum Status{
    QUEUE,  // 隊列中
    RUN,    // 運行中
    WAIT,   // 等待(延時)執行
};

  • 任務類型
enum Type{
    LOOP,   // 循環任務
    ONCE    // 單次任務
};
  • runTime結構
// 任務函數類型
typedef void(*Task)(Array runTimeQueue, let args);

// 啓動任務函數類型
typedef void(*Start)(Array runTimeQueue, struct runTime * runTime);

typedef struct runTime{
    // 用戶任務
    Task task;
    
    // 執行狀態
    Status status;

    // 任務類型
    Type type;

    // 任務啓動函數
    Start start;

    // 任務創建時間戳
    int createTimeStamp;

    // 循環任務間隔時間
    int millisec;

    // 回調傳參
    let args;

} *RunTime;

#define newRunTime() NEW(struct runTime)

// 執行任務
#define runTask(runTime, runTimeQueue) (runTime->start)(runTimeQueue, runTime)

// 任務隊列是否爲空
#define isEmpty(runTimeQueue) runTimeQueue->length == 0

// 創建任務隊列
Array createRunTimeQueue();

// 添加普通任務
int nextTick(Array runTimeQueue, Task task, let args);

// 添加延時執行的任務
int setTimeout(Array runTimeQueue, Task task, let args, int millisec);

// 添加延時循環執行的任務
int setInterval(Array runTimeQueue, Task task, let args, int millisec);

// 開始執行任務隊列
void beginDealTaskQueue(Array runTimeQueue);

// 循環任務
void loop(Array runTimeQueue, RunTime runTime);

// 普通任務
void once(Array runTimeQueue, RunTime runTime);

4. 寫入任務

每一個runTime實例都封裝了一個start啓動器,該函數負責啓動用戶的任務。用戶一共有三種方式寫入任務,nextTick會將任務寫入隊列尾部,啓動器爲once(普通任務),任務類型爲ONCEsetTimeoutsetInterval在寫入的時候都會寫入一個任務創建時間,然後將任務寫入隊列尾部,啓動器爲loop(循環任務),事件循環取到該任務時啓動器會先判斷距離創建時間是否已經達到指定的延時,達到會立刻執行,沒有達到會重新接入隊列尾部,不同的是setTimeout只執行一次,setInterval會無限次執行。代碼實現如下:

#include "task.h"

Array createRunTimeQueue()
{
    return createArray(0);
}

int nextTick(Array runTimeQueue, Task task, let args)
{
    RunTime runTime = newRunTime();
    runTime->status = QUEUE;
    runTime->type = ONCE;
    runTime->task = task;
    runTime->start = once;
    runTime->createTimeStamp = clock();
    runTime->millisec = 0;
    runTime->args = args;
    return runTimeQueue->push(runTimeQueue, runTime);
}

void beginDealTaskQueue(Array runTimeQueue)
{
    while (!isEmpty(runTimeQueue)) {
        // 逐個執行任務隊列
        RunTime runTime = (RunTime)runTimeQueue->shift(runTimeQueue);
        runTask(runTime, runTimeQueue);
    }
}

int setTimeout(Array runTimeQueue, Task task, let args, int millisec)
{
    RunTime runTime = newRunTime();
    runTime->start = loop;
    runTime->status = QUEUE;
    runTime->type = ONCE;
    runTime->task = task;
    runTime->createTimeStamp = clock();
    runTime->millisec = millisec;
    return runTimeQueue->push(runTimeQueue, runTime);
}

int setInterval(Array runTimeQueue, Task task, let args, int millisec)
{
    RunTime runTime = newRunTime();
    runTime->start = loop;
    runTime->status = QUEUE;
    runTime->type = LOOP;
    runTime->task = task;
    runTime->createTimeStamp = clock();
    runTime->millisec = millisec;
    runTime->args = args;
    return runTimeQueue->push(runTimeQueue, runTime);
}

void loop(Array runTimeQueue, RunTime runTime)
{
    int timeStamp = clock();
    if (timeStamp > runTime->createTimeStamp + runTime->millisec) {
        runTime->status = RUN;
        runTime->task(runTimeQueue, runTime->args);
        if (runTime->type == LOOP) {
            // 如果是循環任務則需要再次入隊列
            runTime->createTimeStamp = timeStamp;
            runTimeQueue->push(runTimeQueue, runTime);
        }
        else {
            free(runTime);
        }
    }
    else {
        // 未到達指定時間時將任務入隊列,等待下次執行
        runTime->status = WAIT;
        runTimeQueue->push(runTimeQueue, runTime);
    }
}

void once(Array runTimeQueue, RunTime runTime)
{
    runTime->status = RUN;
    runTime->task(runTimeQueue, runTime->args);
    free(runTime);
}

上一篇:使用C語言模擬node(一)——數據結構
下一篇:使用C語言模擬node(三)——Event模塊

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