libhv学习笔记3:hloop_process_events分析

hloop_process_events函数在上一篇中已经看过,今天深入看一下这部分代码。
如有理解错误,欢迎批评指正。

代码总览

// hloop_process_ios -> hloop_process_timers -> hloop_process_idles -> hloop_process_pendings
static int hloop_process_events(hloop_t* loop) {
    // ios -> timers -> idles
    int nios, ntimers, nidles;
    nios = ntimers = nidles = 0;

    // calc blocktime
    int32_t blocktime = HLOOP_MAX_BLOCK_TIME;
    if (loop->timers.root) {
        hloop_update_time(loop);
        uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
        int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
        if (blocktime_us <= 0) goto process_timers;
        blocktime = blocktime_us / 1000;
        ++blocktime;
        blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
    }

    if (loop->nios) {
        nios = hloop_process_ios(loop, blocktime);
    }
    else {
        msleep(blocktime);
    }
    hloop_update_time(loop);
    // wakeup by hloop_stop
    if (loop->status == HLOOP_STATUS_STOP) {
        return 0;
    }

process_timers:
    if (loop->ntimers) {
        ntimers = hloop_process_timers(loop);
    }

    int npendings = loop->npendings;
    if (npendings == 0) {
        if (loop->nidles) {
            nidles= hloop_process_idles(loop);
        }
    }
    int ncbs = hloop_process_pendings(loop);
    // printd("blocktime=%d nios=%d/%u ntimers=%d/%u nidles=%d/%u nactives=%d npendings=%d ncbs=%d\n",
    //         blocktime, nios, loop->nios, ntimers, loop->ntimers, nidles, loop->nidles,
    //         loop->nactives, npendings, ncbs);
    return ncbs;
}

首先看一下与定时器相关的代码

    if (loop->timers.root) {
        hloop_update_time(loop);
        uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
        int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
        if (blocktime_us <= 0) goto process_timers;
        blocktime = blocktime_us / 1000;
        ++blocktime;
        blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
    }

if (blocktime_us <= 0) goto process_timers;

从这部分代码可以看出,对于超时事件,libhv会优先处理。

更新loop时间

在获取下一个要处理的超时事件前,先调用了hloop_update_time(loop),这个函数是在修改操作系统时间后,调整事件循环的开始时间(start_ms)。具体看一下是怎么做的。

void hloop_update_time(hloop_t* loop) {
    loop->cur_hrtime = gethrtime_us();          
    if (ABS((int64_t)hloop_now(loop) - (int64_t)time(NULL)) > 1) {
        // systemtime changed, we adjust start_ms
        loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000;
    }
}

首先用gethrtime_us()获取了当前时间

unsigned long long gethrtime_us() {
#ifdef OS_WIN
    static LONGLONG s_freq = 0;
    if (s_freq == 0) {
        LARGE_INTEGER freq;
        QueryPerformanceFrequency(&freq);
        s_freq = freq.QuadPart;
    }
    if (s_freq != 0) {
        LARGE_INTEGER count;
        QueryPerformanceCounter(&count);
        return (unsigned long long)(count.QuadPart / (double)s_freq * 1000000);
    }
    return 0;
#elif defined(OS_SOLARIS)
    return gethrtime() / 1000;
#elif HAVE_CLOCK_GETTIME
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec*(unsigned long long)1000000 + ts.tv_nsec / 1000;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*(unsigned long long)1000000 + tv.tv_usec;
#endif
}

在Linux上,调用了clock_gettime(CLOCK_MONOTONIC, &ts);获取了当前时间。clock_gettime的作用如下:

int clock_gettime(clockid_t clk_id,struct timespec *tp);
clk_id : 检索和设置的clk_id指定的时钟时间。
CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,
中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间
CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间

所以gethrtime_us()的作用是,从系统启动开始后经过的时间,与系统时间无关,当用户修改了系统时间后,也不受影响。

uint64_t hloop_now(hloop_t* loop) {
    return loop->start_ms / 1000 + (loop->cur_hrtime - loop->start_hrtime) / 1000000;
}

hloop_now()的作用是,从事件循环开始,经过的us数。此时的时间是以设置loop的start_ms的系统时间为基准的。

那么如果用户修改了系统时间,下面的代码就会判定为“真”,并修改事件循环的开始事件。

if (ABS((int64_t)hloop_now(loop) - (int64_t)time(NULL)) > 1) {
        // systemtime changed, we adjust start_ms
        //开始时间 = 当前系统时间(修改后) - (事件循环执行经过的时间)
        loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000;
    }

IO事件

整个IO事件的过程,后面应该会专门写一个,今天先看下如何处理的。

  if (loop->nios) {
        nios = hloop_process_ios(loop, blocktime);
    }

这里blocktime时间就是当前时间和下一个超时事件的时间差,也就是说,IO事件等待的超时时间,不能影响下一个超时事件的处理。

static int hloop_process_ios(hloop_t* loop, int timeout) {
    // That is to call IO multiplexing function such as select, poll, epoll, etc.
    int nevents = iowatcher_poll_events(loop, timeout);
    if (nevents < 0) {
        hloge("poll_events error=%d", -nevents);
    }
    return nevents < 0 ? 0 : nevents;
}

这里只分析用epoll的方式处理io事件。

int iowatcher_poll_events(hloop_t* loop, int timeout) {
    epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher;
    if (epoll_ctx == NULL)  return 0;
    if (epoll_ctx->events.size == 0) return 0;
    //等待IO事件的发生
    int nepoll = epoll_wait(epoll_ctx->epfd, epoll_ctx->events.ptr, epoll_ctx->events.size, timeout);
    if (nepoll < 0) {
        perror("epoll");
        return nepoll;
    }
    //没有IO事件,直接返回
    if (nepoll == 0) return 0;
    int nevents = 0;
    //遍历所有的IO事件 
    for (int i = 0; i < epoll_ctx->events.size; ++i) {
    	//取出io事件
        struct epoll_event* ee = epoll_ctx->events.ptr + i;
        int fd = ee->data.fd;
        uint32_t revents = ee->events;
        if (revents) {
            ++nevents;
            //根据fd从io事件数组中取出io事件对象
            hio_t* io = loop->ios.ptr[fd];
            if (io) {
            	//判断接收到的事件类型
            	/*
				EPOLLIN   当关联的文件可以执行 read ()操作时
				EPOLLHUP  当指定的文件描述符被挂起的时候。
				EPOLLERR  当关联的文件发生错误的时候
				EPOLLOUT  当关联的文件可以执行 write ()操作时
				*/
                if (revents & (EPOLLIN | EPOLLHUP | EPOLLERR)) {
                    io->revents |= HV_READ;
                }
                if (revents & (EPOLLOUT | EPOLLHUP | EPOLLERR)) {
                    io->revents |= HV_WRITE;
                }
                //添加到待执行的链表中
                EVENT_PENDING(io);
            }
        }
        if (nevents == nepoll) break;
    }
    return nevents;
}

定时器事件

    if (loop->ntimers) {
        ntimers = hloop_process_timers(loop);
    }
static int hloop_process_timers(hloop_t* loop) {
    int ntimers = 0;
    htimer_t* timer = NULL;
    uint64_t now_hrtime = hloop_now_hrtime(loop);
    while (loop->timers.root) {
        // NOTE: root of minheap has min timeout.
        timer = TIMER_ENTRY(loop->timers.root);
        //下一个要执行的时间大于当前时间,也就是没有超时的事件
        if (timer->next_timeout > now_hrtime) {
            break;
        }
        //如果不是无限循环的定时器事件,就减少一次重复数
        if (timer->repeat != INFINITE) {
            --timer->repeat;
        }
        //如果重复数为0的话,就从堆里面删除
        if (timer->repeat == 0) {
            // NOTE: Just mark it as destroy and remove from heap.
            // Real deletion occurs after hloop_process_pendings.
            __htimer_del(timer);
        }
        else {
            // NOTE: calc next timeout, then re-insert heap.
            //从堆里面移除
            heap_dequeue(&loop->timers);
            //计算下一次超时时间
            if (timer->event_type == HEVENT_TYPE_TIMEOUT) {
                while (timer->next_timeout <= now_hrtime) {
                    timer->next_timeout += (uint64_t)((htimeout_t*)timer)->timeout * 1000;
                }
            }
            else if (timer->event_type == HEVENT_TYPE_PERIOD) {
                hperiod_t* period = (hperiod_t*)timer;
                timer->next_timeout = (uint64_t)cron_next_timeout(period->minute, period->hour, period->day,
                        period->week, period->month) * 1000000;
            }
            //重新加入到堆中
            heap_insert(&loop->timers, &timer->node);
        }
        //添加到待执行的链表中
        EVENT_PENDING(timer);
        ++ntimers;
    }
    return ntimers;
}

定时器部分的代码相对还是比较容易理解的,核心问题在存放定时器事件的堆是如何设计的,这个以后专门去分析。

空闲事件

  int npendings = loop->npendings;
    if (npendings == 0) {
        if (loop->nidles) {
            nidles= hloop_process_idles(loop);
        }
    }

如果既没有io事件,又没有定时器超时事件,那么就空闲事件

static int hloop_process_idles(hloop_t* loop) {
    int nidles = 0;
    struct list_node* node = loop->idles.next;
    hidle_t* idle = NULL;
    //遍历所有的空闲事件,(这里用的是双向循环链表)
    while (node != &loop->idles) {
        idle = IDLE_ENTRY(node);
        node = node->next;
        if (idle->repeat != INFINITE) {
            --idle->repeat;
        }
        if (idle->repeat == 0) {
            // NOTE: Just mark it as destroy and remove from list.
            // Real deletion occurs after hloop_process_pendings.
            __hidle_del(idle);
        }
        //将所有的空闲事件都加入到待处理的链表中
        EVENT_PENDING(idle);
        ++nidles;
    }
    return nidles;
}

代码也是很简洁,核心是存储空闲事件的双向链表的设计,后续分析。

执行待处理事件

将io事件,定时器超时事件,空闲事件都加入到待处理链表中后,下面看一下是如何处理的。

static int hloop_process_pendings(hloop_t* loop) {
    if (loop->npendings == 0) return 0;

    hevent_t* cur = NULL;
    hevent_t* next = NULL;
    int ncbs = 0;
    // NOTE: invoke event callback from high to low sorted by priority.
    for (int i = HEVENT_PRIORITY_SIZE-1; i >= 0; --i) {
    	//由优先级由高到低,取出相应的链表
        cur = loop->pendings[i];
        while (cur) {
        	//遍历链表,取出事件
            next = cur->pending_next;
            if (cur->pending) {//等待处理标志
                if (cur->active && cur->cb) {
                    cur->cb(cur);//执行回调函数
                    ++ncbs;
                }
                cur->pending = 0;
                // NOTE: Now we can safely delete event marked as destroy.
                if (cur->destroy) {
                    EVENT_DEL(cur);//删除事件
                }
            }
            cur = next;
        }
        loop->pendings[i] = NULL; //清空当前优先级链表
    }
    loop->npendings = 0;
    return ncbs;
}

总结

以上就是对hloop_process_events函数的一个分析,主要了解了如何获取各种待处理的事件的。代码十分的简洁,比较容易理解。但更多核心代码需要进一步的分析,比如定时器用到的堆,IO事件用到的数组,空闲事件用到的链表,以及为什么没有明显看到对用户自定义事件的处理等。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值