libevent事件(三)---event_add和epoll_add

本文详细介绍了libevent库如何处理定时事件、信号及描述符事件,通过使用小根堆管理和调整定时器事件,同时深入探讨了event_add函数的工作原理,以及如何利用epoll进行高效的事件管理。

libevent将定时事件、信号、描述符事件都集成到event中,并且分别维护了相关的链表。
因此,event_addevent_delete主要是操作链表,并且调用底层的api。

1. event_add函数

首先是超时事件,timer事件用一个小根堆来表示,这个堆中的最小时间可以用来select/poll/epoll参数的最大值.
另外,添加事件分为两个内容,首先会调用底层的add回调,它有可能是epoll_ctl(),然后会将该event添加到注册事件的链表中

int
event_add(struct event *ev, const struct timeval *tv)
{
    struct event_base *base = ev->ev_base;//event_base
    const struct eventop *evsel = base->evsel;//evsel为底层的操作
    void *evbase = base->evbase;
    int res = 0;


    //为timer事件分配空间,min_heap_reserve的操作实际上是realloc
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve(&base->timeheap,
            1 + min_heap_size(&base->timeheap)) == -1)
            return (-1);  /* ENOMEM == errno */
    }//reserve

    //添加事件
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        res = evsel->add(evbase, ev);//底层回调函数
        if (res != -1)
            event_queue_insert(base, ev, EVLIST_INSERTED);//event插入队列
    }

    /* 
     * we should change the timout state only if the previous event
     * addition succeeded.
     */
    if (res != -1 && tv != NULL) {//当上一步操作成功时,如果是timer事件将调整timer堆
        struct timeval now;

        /* 
         * we already reserved memory above for the case where we
         * are not replacing an exisiting timeout.
         */
         //表明event已经在timer堆中了,删除旧的
        if (ev->ev_flags & EVLIST_TIMEOUT)
            event_queue_remove(base, ev, EVLIST_TIMEOUT);

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            /* See if we are just active executing this
             * event in a loop
             */
            if (ev->ev_ncalls && ev->ev_pncalls) {
                /* Abort loop */
                *ev->ev_pncalls = 0;
            }

            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }

        gettime(base, &now);//计算时间并插入到timer的小根堆中
        evutil_timeradd(&now, tv, &ev->ev_timeout);//宏定义:timeout= now + tv

        event_queue_insert(base, ev, EVLIST_TIMEOUT);
    }

    return (res);
}

epoll_add函数

上文提到,相同的接口会调用特定的回调,以epoll为例,将调用封装的epoll_add

static int
epoll_add(void *arg, struct event *ev)
{
    struct epollop *epollop = arg;
    struct epoll_event epev = {0, {0}};
    struct evepoll *evep;
    int fd, op, events;

    if (ev->ev_events & EV_SIGNAL)
        return (evsignal_add(ev));

    fd = ev->ev_fd;
    if (fd >= epollop->nfds) {
        /* Extent the file descriptor array as necessary */
        if (epoll_recalc(ev->ev_base, epollop, fd) == -1)
            return (-1);
    }
    evep = &epollop->fds[fd];
    op = EPOLL_CTL_ADD;
    events = 0;
    if (evep->evread != NULL) {
        events |= EPOLLIN;
        op = EPOLL_CTL_MOD;
    }
    if (evep->evwrite != NULL) {
        events |= EPOLLOUT;
        op = EPOLL_CTL_MOD;
    }

    if (ev->ev_events & EV_READ)
        events |= EPOLLIN;
    if (ev->ev_events & EV_WRITE)
        events |= EPOLLOUT;

    epev.data.fd = fd;
    epev.events = events;
    if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1)
            return (-1);

    /* Update events responsible */
    if (ev->ev_events & EV_READ)
        evep->evread = ev;
    if (ev->ev_events & EV_WRITE)
        evep->evwrite = ev;

    return (0);
}

首先来看传进来的两个参数,在event_add中有如下调用evsel->add(evbase,ev),这个时候就明白evbase的作用了,在event_base_new调用中,
base->evbase=base->evsel->init(base),回调函数将最终调用epoll_init创建一个struct epollop结构体,并返回指针,为什么要这么做?
本质上来说,还是为了可移植性,对于select也会有struct selectop这样的结构体

再看epoll_add函数,将封装的struct eventstruct epollop结构体,转化为epoll认识的struct epoll_event

在 C 语言中使用 `libevent` 库时,通过 `event_add()` 添加的定时回调事件本身 **没有硬性上限**,但实际可添加的定时事件数量受系统资源 `libevent` 内部实现机制的限制。 ### 说明: - `libevent` 使用红黑树(或堆结构,取决于后端)来管理定时事件- 每个定时事件本质上是一个 `event` 对象,调用 `event_add()` 后,`libevent` 会将其插入到定时结构中。 - 理论上你可以添加成千上万个定时事件,但过多的定时器会带来以下问题: ### 潜在限制影响因素: 1. **内存消耗**: - 每个 `event` 对象都需要一定的内存开销,大量定时器会占用较多内存。 2. **性能开销**: - 定时器数量过多可能影响事件循环的性能,特别是在频繁添加/删除定时器的情况下。 3. **系统时间精度限制**: - 使用 `event_add()` 添加的定时器精度受限于系统时钟 `libevent` 的后端(如 `select`、`epoll`、`kqueue`)。 4. **libevent 后端限制**: - 某些后端(如 `select`)可能对最大文件描述符有限制,虽然定时器本身不占用 fd,但通常 `libevent` 会使用 `pipe` 或其他机制模拟。 --- ### 最佳实践建议: - 如果你需要成千上万个定时任务,建议使用支持高效定时器管理的后端(如 `kqueue` 或 `epoll`)。 - 使用 `event_base_once()` 一次性定时器时,注意不要频繁调用导致内存碎片。 - 对于周期性任务,可以考虑复用 `event` 对象,避免频繁 `malloc/free`。 --- ### 示例代码(添加多个定时回调): ```c #include <event2/event.h> #include <stdio.h> void timeout_cb(evutil_socket_t fd, short event, void *arg) { printf("Timeout %d triggered\n", (int)(intptr_t)arg); } int main() { struct event_base *base = event_base_new(); struct event **events = malloc(1000 * sizeof(struct event*)); struct timeval tv = {2, 0}; // 2秒 for (int i = 0; i < 1000; ++i) { events[i] = event_new(base, -1, EV_TIMEOUT, timeout_cb, (void*)(intptr_t)i); event_add(events[i], &tv); } event_base_dispatch(base); // 清理资源 for (int i = 0; i < 1000; ++i) event_free(events[i]); free(events); event_base_free(base); return 0; } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值