使用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模块

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