【Redis-6.0.8】事件循环器AE(上)

Redis源码阅读 ae.c Redis 使用事件驱动的方式实现 IO 多路复用,其核心是基于第三方库的事件循环库。Redis 支持多种事件循环库,包括:libeventlibevae (Redis 自带的事件循环库)其中,Redis 自带的事件循环库 ae 较为轻量级,且支持跨平台,因此 Redis 默认使用 ae 作为事件循环库。在 Redis 启动时,会创建一个事件循环对象,并注册一些必要的事件。事件循环对象中包含了一个事件池和一个定时器队列。是 Redis 中的一个事件循环库,是实现 Redis 事件驱动模型的核心。 阅读详情

目录

 

0.阅读

1.引言

1.1 redis中的事件分类

1.2 为兼容各平台写的代码

1.3 linux上巧妙借用系统函数epoll_wait设置阻塞时间以达到触发条件的代码

1.4 两种事件的调度

2.源码阅读

2.1 ae.h中提供的内容

2.1.1 宏

2.1.2  重定义函数原型

2.1.3 aeFileEvent(IO事件)结构体的定义

2.1.4 aeTimeEvent(定时器事件)结构体的定义

2.1.5 aeFiredEvent(管理就绪事件)结构体的定义

2.1.6 aeEventLoop(事件循环)结构体的定义

2.1.7 ae.h会提供的函数声明

2.2  与AE流程相关的代码逻辑

2.2.1 initServer中与AE有关的初始化

2.2.2 aeMain中的事件循环

2.2.3 全文搜索设置的wfileProc和rfileProc

2.2.4 读事件,写事件与时间事件的处理逻辑


0.阅读

My-基础回顾

图文并茂-说明了flag的用处-初始为AE_NOE

大佬的redis源码注释

redis源码分析—AE事件处理机制

redis事件模型源码分析2:aeEvent模型

Redis源码剖析和注释(十九)--- Redis 事件处理实现

Redis源码分析(二十)--- ae事件驱动

为什么redis取出来是null_Redis事件循环器(AE)实现剖析

Linux网络编程---I/O复用模型之epoll-复习

回调函数与钩子函数

函数指针与回调函数详解

evport-Event ports-Solaris 10

参考-较新版本的代码分析-有AE_BARRIE

aeSetDontWait的理解

复习C语言中的运算符的优先级

侦听fd与客户端fd是如何挂载到EPFD上去的-有系列文章

网络套接字和本地套接字

当心!TCP本机客户端连接本机服务器-dog250

1.引言

1.1 redis中的事件分类

redis中有两类事件:

  1. IO事件,统一封装成aeFileEvent,底层调用系统支持的多路复用层(evport、epoll、kqueue、select);
  2. 定时器事件,以aeTimeEvent描述,巧妙借用系统函数epoll_wait等阻塞函数设置阻塞时间以达到触发条件. 

1.2 为兼容各平台写的代码

// 根据宏定义包含系统支持的多路复用模型,从上到下按性能高低排序
#ifdef HAVE_EVPORT
#include "ae_evport.c"  // solaris
#else
    #ifdef HAVE_EPOLL
    #include "ae_epoll.c"      // linux 
    #else
        #ifdef HAVE_KQUEUE
        #include "ae_kqueue.c" // freeBSD & macosx
        #else
        #include "ae_select.c" // windows
        #endif
    #endif
#endif

1.3 linux上巧妙借用系统函数epoll_wait设置阻塞时间以达到触发条件的代码

retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

1.4 两种事件的调度

如上,Redis中有两种事件,分别是文件事件和时间事件,它们通过epoll_wait(linux下)巧妙地产生了联系.

调度指的是什么时候执行文件事件,什么时候执行时间事件. 两种事件都是在aeProcessEvents中进行处理.
大致流程是这样的,首先遍历时间事件的链表找出离现在最近的时间事件的时间间隔,根据这个最小时间间隔来
设置io多路复用接口的等待时间(即linux上的epoll_wait中设置的等待时间).此时并没有执行时间事件,要先
处理文件事件再处理时间事件.
比如说:serverCron函数每100ms执行一次,现在是50ms, 假设多路复用是用的epoll,那么就设置epoll_wait
的等待时间为50,这样假如没有网络事件到达50m后就返回去处理时间事件.另一种情况,如果检测到时间事件已
经到达了,那么就设置epoll_wait的等待时间为0,这样不管有没有网络事件发生epoll_wait也会立即返回,因
为有时间事件等着去处理.

 

2.源码阅读

2.1 ae.h中提供的内容

2.1.1 宏

/* aeResizeSetSize,aeCreateFileEvent,aeDeleteTimeEvent函数的可能返回值*/
#define AE_OK 0  
/* 作为众多ae系列函数的返回值使用 */
#define AE_ERR -1
/* 没有事件注册上 */
#define AE_NONE 0       /* No events registered. */
/* 有读事件注册上 */
#define AE_READABLE 1   /* Fire when descriptor is readable. */
/* 有写事件注册上 */
#define AE_WRITABLE 2   /* Fire when descriptor is writable. */
/* 
   一般情况先执行readable事件再执行writable事件,
   这里的AE_BARRIER表示事件屏障,设置了AE_BARRIER之后,如果已经有了readable事件之后就不会触发 
   writable事件,在如下场景下适用:
   例如:你想要的以批量的方式先将file同步到磁盘,然后再回复给客户端
   这种情况下先执行writable事件再执行readable事件.
   感觉
    
   这里还需要再多多理解,不是很明白.(本段解释可能不对,感觉这里的英文解释写的不是很好,据下面
   的说,应该就是如果设置了AE_BARRIER,就优先处理写事件)
*/
#define AE_BARRIER 4    /* With WRITABLE, never fire the event if the
                           READABLE event already fired in the same event
                           loop iteration. Useful when you want to persist
                           things to disk before sending replies, and want
                           to do that in a group fashion. */
/* 0001-表示文件事件 */
#define AE_FILE_EVENTS (1<<0)
/* 0010-表示时间事件 */
#define AE_TIME_EVENTS (1<<1)
/* 0011-表示文件事件和时间事件 */
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
/* 0100-表示函数处理完事件后直接返回,不阻塞等待 */
#define AE_DONT_WAIT (1<<2)
/* 
1000-用于aeProcessEvents(aeEventLoop *eventLoop, int flags)等处,如果
flags设置了AE_CALL_BEFORE_SLEEP,eventLoop->beforesleep回调函数会被调用
*/
#define AE_CALL_BEFORE_SLEEP (1<<3)
/* 
10000-用于aeProcessEvents(aeEventLoop *eventLoop, int flags)等处,如果
flags设置了AE_CALL_BEFORE_SLEEP,eventLoop->aftersleep回调函数会被调用
*/
#define AE_CALL_AFTER_SLEEP (1<<4)
/* 表示没有事件了,在processTimeEvents中被用到 */
#define AE_NOMORE -1
/* 表示删除事件ID,在processTimeEvents,aeDeleteTimeEvent等函数中被用到 */
#define AE_DELETED_EVENT_ID -1
/* 仅仅用作编译器处理,防止因为没有到相关变量而被当做错误 */
#define AE_NOTUSED(V) ((void) V)

2.1.2  重定义函数原型

/* Types and data structures */
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);

aeFileProc          -有IO事件时处理IO事件的函数原型
aeTimeProc          -有时间事件时处理时间事件的函数原型
aeEventFinalizerProc-一个对eventLoop和clientData处理的函数原型
aeBeforeSleepProc   -一个对eventLoop处理的函数原型,后面此函数类型具体的对象有beforeSleep和afterSleep

2.1.3 aeFileEvent(IO事件)结构体的定义

/* IO事件结构体 */
/* File event structure */
typedef struct aeFileEvent {
    /* 文件事件类型:是AE_READABLE,AE_WRITABLE和AE_BARRIER中的一个 */
    int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */
    /* 有可读IO事件时的处理函数 */
    aeFileProc *rfileProc;
    /* 有可写IO事件时的处理函数 */
    aeFileProc *wfileProc;
    /* 客户端传入的数据 */
    void *clientData;
} aeFileEvent;

2.1.4 aeTimeEvent(定时器事件)结构体的定义

/* Time event structure */
typedef struct aeTimeEvent {
    /* 时间事件的id */
    long long id; /* time event identifier. */
    /* 时间事件到达的指定时间的秒数 */
    long when_sec; /* seconds */
    /* 时间事件到达的指定时间的毫秒数*/
    long when_ms; /* milliseconds */
    /* 时间事件处理函数 */
    aeTimeProc *timeProc;
    /* 时间事件终结函数 */
    aeEventFinalizerProc *finalizerProc;
    /* 客户端传入的数据 */
    void *clientData;
    /* 指向上一个时间事件的指针 */
    struct aeTimeEvent *prev;
    /* 指向下一个时间事件的指针 */
    struct aeTimeEvent *next;
    /* 引用次数,防止时间事件在多次被调用后被释放*/
    int refcount; /* refcount to prevent timer events from being
  		           * freed in recursive time event calls. */
} aeTimeEvent;

2.1.5 aeFiredEvent(管理就绪事件)结构体的定义

/* 就绪事件 */
/* A fired event */
typedef struct aeFiredEvent {
    /* 就绪事件的文件描述符 */
    int fd;
    /* 就绪事件类型,如AE_NONE,AE_READABLE,AE_WRITABLE等 */
    int mask;
} aeFiredEvent;

2.1.6 aeEventLoop(事件循环)结构体的定义

/* State of an event based program */
typedef struct aeEventLoop {
    /* 当前已注册的最大的文件描述符 */
    int maxfd;   /* highest file descriptor currently registered */
    /* 文件描述符监听集合的大小 */
    int setsize; /* max number of file descriptors tracked */
    /* 下一个时间事件的ID */
    long long timeEventNextId;
    /* 最后一次执行事件的时间 */
    time_t lastTime;     /* Used to detect system clock skew */
    /* 已注册的事件 */
    aeFileEvent *events; /* Registered events */
    /* 已触发的事件 */
    aeFiredEvent *fired; /* Fired events */
    /* 时间事件的头节点指针 */
    aeTimeEvent *timeEventHead;
    /* 事件处理开关 */
    int stop;
    /* 多路复用库的事件状态数据 */
    void *apidata; /* This is used for polling API specific data */
    /* 执行处理事件之前的函数 */
    aeBeforeSleepProc *beforesleep;
    /* 执行处理事件之后的函数 */
    aeBeforeSleepProc *aftersleep;
    /* 事件循环的标志,初始值赋值为NONE(0),在aeCreateEventLoop中搜索eventLoop->flags可看到*/
    int flags;
} aeEventLoop;

2.1.7 ae.h会提供的函数声明

/* 创建aeEventLoop */
aeEventLoop *aeCreateEventLoop(int setsize);
/* 删除EventLoop,释放相应的事件所占的空间 */
void aeDeleteEventLoop(aeEventLoop *eventLoop);
/* 设置eventLoop中的停止属性为1,服务器中似乎没有用到,压测和客户端中有用到这个函数 */
void aeStop(aeEventLoop *eventLoop); 
/* 在eventLoop中创建文件事件 */
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData);
/* 删除文件事件 */
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
/* 根据文件描述符id,找出文件的属性,是读事件还是写事件 */
int aeGetFileEvents(aeEventLoop *eventLoop, int fd);
/* 在eventLoop中添加时间事件,创建的时间为当前时间加上自己传入的时间 */
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
        aeTimeProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc);
/* 根据时间id,删除时间事件,涉及链表的操作 */
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
/* 处理eventLoop中的所有类型事件 */
int aeProcessEvents(aeEventLoop *eventLoop, int flags);
/* 让某事件等待 */
int aeWait(int fd, int mask, long long milliseconds);
/* ae事件执行主程序 */
void aeMain(aeEventLoop *eventLoop);
/* 获取接口名 */
char *aeGetApiName(void);
/* 设置eventLoop->beforesleep回调函数*/
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
/* 设置eventLoop->aftersleep回调函数 */
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
/* 获取eventLoop的长度*/
int aeGetSetSize(aeEventLoop *eventLoop);
/* 设置eventLoop的长度*/
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
/* 通知事件的下一个迭代器将超时设置为零,即不等待 */
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);

2.2  与AE流程相关的代码逻辑

int main(){
    ...
    initServer();
    ...
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
    ...
}

2.2.1 initServer中与AE有关的初始化

一个server.c的注释

2.2.1.1 initServer中的重点函数

int main(){
    ...
    initServer();
    ...
}

void initServer(void) {

/* 创建侦听 fd */
/* Open the TCP listening socket for the user commands. */
    if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
    if (server.tls_port != 0 &&
        listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
        exit(1);
/* 将侦听fd设置为非阻塞的 */
// anetNonBlock  // 在listenToPort调用到了,initServer中也可能调用到
...
/* 初始化server.el ,注意在这里的aeCreateEventLoop内部调用了epoll_create */
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    if (server.el == NULL) {
        serverLog(LL_WARNING,
            "Failed creating the event loop. Error message: '%s'",
            strerror(errno));
        exit(1);
    }
...
    /* 创建Redis的定时器,设置定时器的回调函数为serverCron */
    /* Create the timer callback, this is our way to process many background
     * operations incrementally, like clients timeout, eviction of unaccessed
     * expired keys and so forth. */
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }
    /* 设置TCP和Unix本地套接字接受新链接时的事件响应函数 */
    /* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
            acceptTcpHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.ipfd file event.");
            }
    }
    for (j = 0; j < server.tlsfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
            acceptTLSHandler,NULL) == AE_ERR)
            {
                serverPanic(
                    "Unrecoverable error creating server.tlsfd file event.");
            }
    }
    if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
        acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");

    /*为管道注册一个用于唤醒事件循环的可读事件,需要注意模块中被阻塞的客户端 */
    /* Register a readable event for the pipe used to awake the event loop
     * when a blocked client in a module needs attention. */
    if (aeCreateFileEvent(server.el, server.module_blocked_pipe[0], AE_READABLE,
        moduleBlockedClientPipeReadable,NULL) == AE_ERR) {
            serverPanic(
                "Error registering the readable event for the module "
                "blocked clients subsystem.");
    }
    /*  注册before和after睡眠函数(注意要在加载持久化的数据之前进行,因为它会被 
        processEventsWhileBlocked函数用到 */
    /* Register before and after sleep handlers (note this needs to be done
     * before loading persistence since it is used by processEventsWhileBlocked. */
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeSetAfterSleepProc(server.el,afterSleep);
...
}


网络编程关键步骤:
listenToPort      -- 监听端口
anetNonBlock      -- 将侦听fd设置为非阻塞的
aeCreateEventLoop -- 调用epoll_create
acceptTcpHandler  -- 此处仅设置回调函数,回调函数中实现将侦听fd绑定到epfd上去,触发的时候调用



问题:
aeCreateFileEvent的原型是:
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, aeFileProc *proc, void *clientData)
{
   ...
   if (mask & AE_READABLE) fe->rfileProc = proc;
   if (mask & AE_WRITABLE) fe->wfileProc = proc;
   ...
}

相关调用:
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL);
aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,acceptTLSHandler,NULL);
aeCreateFileEvent(server.el,server.module_blocked_pipe[0],AE_READABLE,moduleBlockedClientPipeReadable,NULL);
aeCreateFileEvent(server.el,server.sofd,AE_READABLE,acceptUnixHandler,NULL);

问:
调用aeCreateFileEvent的时候第二个参数分别为:
server中的 
          ipfd,
          tlsfd,
          module_blocked_pipe,
          sofd,
这四个参数分别都代表什么意思?

注册的回调函数为:
acceptTcpHandler -- 用于accept client的connect
acceptTLSHandler -- 用于acceptclient的本地connect
moduleBlockedClientPipeReadable
acceptUnixHandler
这四个回调函数有什么不同的用途?




补充:不仅仅在初始化的时候有AE相关操作,在全局静态区也有,如
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);

2.2.1.2 稍微看下aeCreateEventLoop-内部执行了epoll_create

aeEventLoop *aeCreateEventLoop(int setsize) {
    aeEventLoop *eventLoop;
    int i;
    if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
    eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
    eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
    if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
    eventLoop->setsize = setsize;
    eventLoop->lastTime = time(NULL);
    eventLoop->timeEventHead = NULL;
    eventLoop->timeEventNextId = 0;
    eventLoop->stop = 0;
    eventLoop->maxfd = -1;
    eventLoop->beforesleep = NULL;
    eventLoop->aftersleep = NULL;
    eventLoop->flags = 0;
    if (aeApiCreate(eventLoop) == -1) goto err;
    /* Events with mask == AE_NONE are not set. So let's initialize the
     * vector with it. */
    for (i = 0; i < setsize; i++)
        eventLoop->events[i].mask = AE_NONE;
    return eventLoop;

err:
    if (eventLoop) {
        zfree(eventLoop->events);
        zfree(eventLoop->fired);
        zfree(eventLoop);
    }
    return NULL;
}

2.2.2 aeMain中的事件循环

2.2.2.1 aeMain的实现

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}

 2.2.2.2 aeProcessEvents的实现

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    /*
     1.定义临时变量processed(已经处理好的事件数)并初始化;
     2.定义临时变量numevents(事件数)
    */
    int processed = 0, numevents;

    /* 如果不处理定时器事件,也不处理文件事件,就立即直接返回 */    
    /* Nothing to do? return ASAP */
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
     
    /* 请注意,既然我们要处理时间事件,即使没有要处理的文件事件,我们仍要调用select(),以便在下
       一次事件准备启动之前进行休眠 */
    /* Note that we want call select() even if there are no
     * file events to process as long as we want to process time
     * events, in order to sleep until the next time event is ready
     * to fire. */
    /* 如果【有监控文件事件】或者【有要处理定时器事件并且没有设置不阻塞标志】则进入逻辑*/
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int j;
        aeTimeEvent *shortest = NULL;
        struct timeval tv, *tvp;
        
        /* 如果处理定时器事件且没有设置不阻塞标志,取最快到达的时间事件 */
        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            shortest = aeSearchNearestTimer(eventLoop);
        /* 如果获取的最快到达的时间事件shortest非空 */
        if (shortest) {
            long now_sec, now_ms;

            aeGetTime(&now_sec, &now_ms); /* 获取当前时间 */
            tvp = &tv;
            
            /* 计算还有多少毫秒能够触发下一次时间事件 */
            /* How many milliseconds we need to wait for the next
             * time event to fire? */
            long long ms =
                (shortest->when_sec - now_sec)*1000 +
                shortest->when_ms - now_ms;
            
            /* 对计算出来的ms做出一些处理,如果ms大于零则将其赋值给临时变量tvp,否则将tvp两部分 
               都置为0*/
            if (ms > 0) {
                tvp->tv_sec = ms/1000;
                tvp->tv_usec = (ms % 1000)*1000;
            } else {
                tvp->tv_sec = 0;
                tvp->tv_usec = 0;
            }
        } 
        else  /* 如果获取的shortest为空,即不存在时间事件 */
        {
            /* 如果设置了不阻塞标志,则将阻塞时间为0,表示不阻塞 */
            /* If we have to check for events but need to return
             * ASAP because of AE_DONT_WAIT we need to set the timeout
             * to zero */
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else { /* 阻塞直到第一个时间事件的到来 */
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */
            }
        }
        
        /* 如果事件循环的标志位不阻塞等待,那么将tv的成员变量值置为0并且将tvp指向tv的地址*/
        if (eventLoop->flags & AE_DONT_WAIT) {
            tv.tv_sec = tv.tv_usec = 0;
            tvp = &tv;
        }
        /* 执行阻塞前的处理函数 */
        if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
            eventLoop->beforesleep(eventLoop);
       
        /* 调用aeApiPoll,这个函数仅仅会返回超时或者当有事件触发的时候回返回就绪文件事件个数
           linux下的epoll_wait的返回值:若成功,返回就绪的文件描述符个数;
                              若出错, 返回-1,
                              若超时, 返回0
           这里只会返回0或者就绪的文件描述符个数.
         */
        /* 阻塞等待文件事件,这个方法在超时或者有事件触发时才会返回 */
        /* Call the multiplexing API, will return only on timeout or when
         * some event fires. */
        numevents = aeApiPoll(eventLoop, tvp);
       
        /* 执行阻塞后的处理函数 */
        /* After sleep callback. */
        if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
            eventLoop->aftersleep(eventLoop);
        

        /* 循环处理触发的文件事件 */
        for (j = 0; j < numevents; j++) {
            /* 获取触发的文件事件 */
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int fired = 0; /* Number of events fired for current fd. */

            /* Normally we execute the readable event first, and the writable
             * event laster. This is useful as sometimes we may be able
             * to serve the reply of a query immediately after processing the
             * query.
             *
             * However if AE_BARRIER is set in the mask, our application is
             * asking us to do the reverse: never fire the writable event
             * after the readable. In such a case, we invert the calls.
             * This is useful when, for instance, we want to do things
             * in the beforeSleep() hook, like fsynching a file to disk,
             * before replying to a client. */

            /* 查看事件是否设置AE_BARRIER标志,如果设置了AE_BARRIER标志,优先处理写事件 */
            int invert = fe->mask & AE_BARRIER;

            /* Note the "fe->mask & mask & ..." code: maybe an already
             * processed event removed an element that fired and we still
             * didn't processed, so we check if the event is still valid.
             *
             * Fire the readable event if the call sequence is not
             * inverted. */
             /*

             */
             /* 如果没有设置AE_BARRIER标志,优先处理读事件 */
            if (!invert && fe->mask & mask & AE_READABLE) {
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                fired++;
                fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
            }
            
            /*  
                 如果是写事件,且在【没有翻转读写顺序】或【fe->wfileProc与fe->rfileProc】
                 的情况下,执行写事件处理函数
                 疑问:为什么要做【fe->wfileProc与fe->rfileProc】的判断?
                       可能是默写版本中的错误,下面有refresh in case of resize,应该
                       是为了解决一些存在的瞬时状态的bug而产生的更严谨的写法.
            */
            /* Fire the writable event. */
            if (fe->mask & mask & AE_WRITABLE) {
                if (!fired || fe->wfileProc != fe->rfileProc) {
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++;
                }
            }

            /* If we have to invert the call, fire the readable event now
             * after the writable one. */
            /* 如果我们一定要颠倒读事件与写事件的处理顺序 */
            if (invert) {
                /* 重新获取一下文件事件防止被更新 */
                fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
                /* 
                  如果是读事件且没有被置成触发状态且【fe->wfileProc != fe->rfileProc】
                  执行读文件事件处理函数.
                */
                if ((fe->mask & mask & AE_READABLE) &&
                    (!fired || fe->wfileProc != fe->rfileProc))
                {
                    fe->rfileProc(eventLoop,fd,fe->clientData,mask);
                    fired++; // 触发统计值加一
                }
            }
            /* 处理值加一 */
            processed++;
        }
    }
    /* Check time events */
    /* 如果是时间事件 */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);/* 调用时间事件处理函数 */

    return processed; /* return the number of processed file/time events */
}

2.2.2.3 linux下的aeApiPoll-调用了epoll_wait

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;
    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
        int j;
        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE; 
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

/*
复习:

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};
events成员描述事件类型,将以下宏定义通过位或方式组合:
EPOLLIN  :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
POLLOUT  :表示对应的文件描述符可以写
EPOLLPRI :表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR :表示对应的文件描述符发生错误
EPOLLHUP :表示对应的文件描述符被挂断;
EPOLLET  :将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

*/

2.2.3 全文搜索设置的wfileProc和rfileProc

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[fd];

    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}


/* aof.c */
aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,AE_WRITABLE, aofChildWriteDiffData, NULL);

/* cluster.c */
aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,clusterAcceptHandler, NULL);

/* connections */
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,conn->type->ae_handler, conn); 
aeCreateFileEvent(server.el,conn->fd,AE_READABLE,conn->type->ae_handler,conn);

/* rdb.c */
aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL);
aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL);


/* redis-benchmark.c */
aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c);
aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c);
aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c);


/* replication.c */
(aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE,rdbPipeReadHandler,NULL); 

/* sentinel.c */
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); 
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);


/* server.c */
aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL);
aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,acceptTLSHandler,NULL);
aeCreateFileEvent(server.el,server.sofd,AE_READABLE,acceptUnixHandler,NULL);
aeCreateFileEvent(server.el,server.module_blocked_pipe[0],AE_READABLE,moduleBlockedClientPipeReadable,NULL);

2.2.4 读事件,写事件与时间事件的处理逻辑

epoll编程回顾

2.2.3.1 读事件处理-问题

1.关注acceptTcpHandler
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
    char cip[NET_IP_STR_LEN];
    UNUSED(el);
    UNUSED(mask);
    UNUSED(privdata);

    while(max--) {
        /* 返回客户端连接的套接字 */
        cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); 
        if (cfd == ANET_ERR) {
            if (errno != EWOULDBLOCK)
                serverLog(LL_WARNING,
                    "Accepting client connection: %s", server.neterr);
            return;
        }
        serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
        /*
          首先关注connCreateAcceptedSocket(cfd);
          再关注acceptCommonHandler.
        */
        acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
    }
}


2.关注anetTcpAccept,返回连接上的客户端的套接字

2.1 关注anetTcpAccept的实现:
int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
    int fd;
    struct sockaddr_storage sa;
    socklen_t salen = sizeof(sa);
    if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
        return ANET_ERR;

    if (sa.ss_family == AF_INET) {
        struct sockaddr_in *s = (struct sockaddr_in *)&sa;
        if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
        if (port) *port = ntohs(s->sin_port);
    } else {
        struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
        if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
        if (port) *port = ntohs(s->sin6_port);
    }
    return fd;
}

2.2 关注子步骤anetGenericAccept的实现(这里实现了socket的accept)
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {
    int fd;
    while(1) {
        fd = accept(s,sa,len);
        if (fd == -1) {
            if (errno == EINTR)
                continue;
            else {
                anetSetError(err, "accept: %s", strerror(errno));
                return ANET_ERR;
            }
        }
        break;
    }
    return fd;
}




3.关注connCreateAcceptedSocket(cfd),函数得到一个connection类型的指针,其工作为:
  (1)执行connCreateSocket(),将conn->type指向CT_Socket这个变量,从而得到CT_Socket所拥有的的 
     一切特性,其实CT_Socket所拥有的就是一系列的操作函数;
  (2)将fd赋值给链接的fd;
  (3)将链接的state设置为CONN_STATE_ACCEPTING;
  (4)返回conn.

connection *connCreateAcceptedSocket(int fd) {
    connection *conn = connCreateSocket();
    conn->fd = fd;
    conn->state = CONN_STATE_ACCEPTING;
    return conn;
}


connection *connCreateSocket() {
    connection *conn = zcalloc(sizeof(connection)); // 申请内存
    conn->type = &CT_Socket; // 初始化type的值
    conn->fd = -1;           // 初始化fd的值
    return conn;             // 返回conn
}

// struct connection 的定义
typedef struct connection connection;
struct connection {
    ConnectionType *type; // type指向一系列操作struct connection实例对象的函数
    ConnectionState state;
    short int flags;
    short int refs;
    int last_errno;
    void *private_data;
    ConnectionCallbackFunc conn_handler;
    ConnectionCallbackFunc write_handler;
    ConnectionCallbackFunc read_handler;
    int fd;
};


// ConnectionType 的定义
typedef struct ConnectionType {
    void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
    int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
    int (*write)(struct connection *conn, const void *data, size_t data_len);
    int (*read)(struct connection *conn, void *buf, size_t buf_len);
    void (*close)(struct connection *conn);
    int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
    int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
    int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
    const char *(*get_last_error)(struct connection *conn);
    int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
    ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
    ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
    ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
    int (*get_type)(struct connection *conn);
} ConnectionType;

// CT_Socket 变量的初始化
ConnectionType CT_Socket = {
    .ae_handler = connSocketEventHandler,          // 被下面三处注册为回调函数,下面有实现
    .close = connSocketClose,
    .write = connSocketWrite,
    .read = connSocketRead,
    .accept = connSocketAccept,  
    .connect = connSocketConnect,                   // 注册了回调函数
    .set_write_handler = connSocketSetWriteHandler, // 注册了回调函数
    .set_read_handler = connSocketSetReadHandler,   // 注册了回调函数
    .get_last_error = connSocketGetLastError,
    .blocking_connect = connSocketBlockingConnect,
    .sync_write = connSocketSyncWrite,
    .sync_read = connSocketSyncRead,
    .sync_readline = connSocketSyncReadLine,
    .get_type = connSocketGetType
};

/* 
注意:
(1)connSocketConnect,connSocketSetWriteHandler
调用了aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,conn->type->ae_handler, conn); 

(2)connSocketSetReadHandler
调用了aeCreateFileEvent(server.el,conn->fd,AE_READABLE,conn->type->ae_handler,conn)

*/

如果有一个struct connection类型的指针变量为conn,那么它就会有如下的操作函数:
conn->type->ae_handler
conn->type->close 
conn->type->write 
conn->type->read 
conn->type->accept 
conn->type->connect 
conn->type->set_write_handler 
conn->type->set_read_handler 
conn->type->get_last_error 
conn->type->blocking_connect 
conn->type->sync_write 
conn->type->sync_read 
conn->type->sync_readline 
conn->type->get_type 




/* 函数connSocketEventHandler的实现 */
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
{
    UNUSED(el);
    UNUSED(fd);
    connection *conn = clientData;

    if (conn->state == CONN_STATE_CONNECTING &&
            (mask & AE_WRITABLE) && conn->conn_handler) {

        if (connGetSocketError(conn)) {
            conn->last_errno = errno;
            conn->state = CONN_STATE_ERROR;
        } else {
            conn->state = CONN_STATE_CONNECTED;
        }

        if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);

        if (!callHandler(conn, conn->conn_handler)) return;
        conn->conn_handler = NULL;
    }

    /* Normally we execute the readable event first, and the writable
     * event later. This is useful as sometimes we may be able
     * to serve the reply of a query immediately after processing the
     * query.
     *
     * However if WRITE_BARRIER is set in the mask, our application is
     * asking us to do the reverse: never fire the writable event
     * after the readable. In such a case, we invert the calls.
     * This is useful when, for instance, we want to do things
     * in the beforeSleep() hook, like fsync'ing a file to disk,
     * before replying to a client. */
    int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;

    int call_write = (mask & AE_WRITABLE) && conn->write_handler;
    int call_read = (mask & AE_READABLE) && conn->read_handler;

    /* Handle normal I/O flows */
    if (!invert && call_read) {
        if (!callHandler(conn, conn->read_handler)) return;
    }
    /* Fire the writable event. */
    if (call_write) {
        if (!callHandler(conn, conn->write_handler)) return;
    }
    /* If we have to invert the call, fire the readable event now
     * after the writable one. */
    if (invert && call_read) {
        if (!callHandler(conn, conn->read_handler)) return;
    }
}



/* Register a write handler, to be called when the connection is writable.
 * If NULL, the existing handler is removed.
 *
 * The barrier flag indicates a write barrier is requested, resulting with
 * CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
 * always called before and not after the read handler in a single event
 * loop.
 */
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
    if (func == conn->write_handler) return C_OK;

    conn->write_handler = func;
    if (barrier)
        conn->flags |= CONN_FLAG_WRITE_BARRIER;
    else
        conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
    if (!conn->write_handler)
        aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
    else
        if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
                    conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
    return C_OK;
}


/* Register a read handler, to be called when the connection is readable.
 * If NULL, the existing handler is removed.
 */
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
    if (func == conn->read_handler) return C_OK;

    conn->read_handler = func;
    if (!conn->read_handler)
        aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
    else
        if (aeCreateFileEvent(server.el,conn->fd,
                    AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
    return C_OK;
}


4. 关注acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);(在大步骤1中最后一步)
acceptCommonHandler的实现:
static void acceptCommonHandler(connection *conn, int flags, char *ip) {
    client *c;
    char conninfo[100];
    UNUSED(ip);

    if (connGetState(conn) != CONN_STATE_ACCEPTING) {
        serverLog(LL_VERBOSE,
            "Accepted client connection in error state: %s (conn: %s)",
            connGetLastError(conn),
            connGetInfo(conn, conninfo, sizeof(conninfo)));
        connClose(conn);
        return;
    }

    /* Limit the number of connections we take at the same time.
     *
     * Admission control will happen before a client is created and connAccept()
     * called, because we don't want to even start transport-level negotiation
     * if rejected. */
    if (listLength(server.clients) + getClusterConnectionsCount()
        >= server.maxclients)
    {
        char *err;
        if (server.cluster_enabled)
            err = "-ERR max number of clients + cluster "
                  "connections reached\r\n";
        else
            err = "-ERR max number of clients reached\r\n";

        /* That's a best effort error message, don't check write errors.
         * Note that for TLS connections, no handshake was done yet so nothing
         * is written and the connection will just drop. */
        if (connWrite(conn,err,strlen(err)) == -1) {
            /* Nothing to do, Just to avoid the warning... */
        }
        server.stat_rejected_conn++;
        connClose(conn);
        return;
    }

    /* Create connection and client */
    if ((c = createClient(conn)) == NULL) {
        serverLog(LL_WARNING,
            "Error registering fd event for the new client: %s (conn: %s)",
            connGetLastError(conn),
            connGetInfo(conn, conninfo, sizeof(conninfo)));
        connClose(conn); /* May be already closed, just ignore errors */
        return;
    }

    /* Last chance to keep flags */
    c->flags |= flags;

    /* Initiate accept.
     *
     * Note that connAccept() is free to do two things here:
     * 1. Call clientAcceptHandler() immediately;
     * 2. Schedule a future call to clientAcceptHandler().
     *
     * Because of that, we must do nothing else afterwards.
     */
    if (connAccept(conn, clientAcceptHandler) == C_ERR) {
        char conninfo[100];
        if (connGetState(conn) == CONN_STATE_ERROR)
            serverLog(LL_WARNING,
                    "Error accepting a client connection: %s (conn: %s)",
                    connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
        freeClient(connGetPrivateData(conn));
        return;
    }
}

2.2.3.2 写事件处理-问题

2.2.3.3 时间事件处理

/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
    int processed = 0;
    aeTimeEvent *te;
    long long maxId;
    time_t now = time(NULL);

    /* If the system clock is moved to the future, and then set back to the
     * right value, time events may be delayed in a random way. Often this
     * means that scheduled operations will not be performed soon enough.
     *
     * Here we try to detect system clock skews, and force all the time
     * events to be processed ASAP when this happens: the idea is that
     * processing events earlier is less dangerous than delaying them
     * indefinitely, and practice suggests it is. */
    if (now < eventLoop->lastTime) {
        te = eventLoop->timeEventHead;
        while(te) {
            te->when_sec = 0;
            te = te->next;
        }
    }
    eventLoop->lastTime = now;

    te = eventLoop->timeEventHead;
    maxId = eventLoop->timeEventNextId-1;
    while(te) {
        long now_sec, now_ms;
        long long id;

        /* Remove events scheduled for deletion. */
        if (te->id == AE_DELETED_EVENT_ID) {
            aeTimeEvent *next = te->next;
            /* If a reference exists for this timer event,
             * don't free it. This is currently incremented
             * for recursive timerProc calls */
            if (te->refcount) {
                te = next;
                continue;
            }
            if (te->prev)
                te->prev->next = te->next;
            else
                eventLoop->timeEventHead = te->next;
            if (te->next)
                te->next->prev = te->prev;
            if (te->finalizerProc)
                te->finalizerProc(eventLoop, te->clientData);
            zfree(te);
            te = next;
            continue;
        }

        /* Make sure we don't process time events created by time events in
         * this iteration. Note that this check is currently useless: we always
         * add new timers on the head, however if we change the implementation
         * detail, this check may be useful again: we keep it here for future
         * defense. */
        if (te->id > maxId) {
            te = te->next;
            continue;
        }
        aeGetTime(&now_sec, &now_ms);
        if (now_sec > te->when_sec ||
            (now_sec == te->when_sec && now_ms >= te->when_ms))
        {
            int retval;

            id = te->id;
            te->refcount++;
            retval = te->timeProc(eventLoop, id, te->clientData);
            te->refcount--;
            processed++;
            if (retval != AE_NOMORE) {
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
            } else {
                te->id = AE_DELETED_EVENT_ID;
            }
        }
        te = te->next;
    }
    return processed;
}

 

如何使用 ABAP 代码调度一个 ABAP 程序,让其以后台作业的方式运行 笔者之前的教程,介绍了如何使用 SM36 一系列事务码,将一个 ABAP 程序,调度成以后台作业方式运行的详细步骤。 38. 如何让 ABAP 报表在后台作业的模式下运行 大家如果照着上述文章的步骤走一遍,会发现这些手动执行的步骤还是有些繁琐的。 有没有可能让这一切都自动化起来,即通过 ABAP 代码去完成呢?当然可以。 阅读详情

相关推荐

uniapp APP分享;判断用户是否安装APP,已安装直接打开,未安装跳转下载页;uniapp 在外部打开APP(schemes)

A将某商品分享给B,B点击后判断是否安装APP,若安装直接打开,没有安装则跳转下载页;

@微你 5682

Redis源代码分析之七:事件驱动库分析——Ae

aeEventLoop是一个记录记录程序事件状态的结构:/* State of an event based program */ typedef struct aeEventLoop { int maxfd; long long timeEventNextId; aeFileEvent events[AE_SETSIZE]; /* Registered events */

Aegeaner的专栏 5494

告别GPS,用Sentinel-1数据+Python实战SBAS-InSAR监测城市地面沉降(保姆级教程)

本文详细介绍了如何利用Sentinel-1数据和Python实现SBAS-InSAR技术监测城市地面沉降。通过保姆级教程,从环境配置、数据获取到干涉处理、时序分析和形变反演,逐步解析全流程技术细节,帮助读者掌握毫米级精度的形变监测方法,并以上海陆家嘴区域为例展示实际应用效果。

weixin_32771907的博客 536

Redis源码整理笔记:ae与个人理解

AE 事件循环器: 一个简单的事件驱动的编程库,程序库封装了网络模型:evport、epoll、kqueue、select 通过宏来控制引入不同模型的.c文件实现,不同操作系统定义不同的宏,编译时会编译对应的文件 linux系统使用epoll,apple/Mac/bsd系统使用kqueue,__sun使用evport(宏__sun实际对应Solaris系统), 其他操作系统如windows则使用...

fengwuya的博客 773

3D Slicer中Auto3DSeg和TotalSegmentator等自动分割插件总是安装失败,怎么办?教你两招

TotalSegmentator, MONAI auto3Dseg,这些工具因分割器官多,准确性高,备受关注。同时,这些自动分割工具被集成在了3D slicer平台,非常方便临床医生做手术规划。详细了解这些工具看之前的教程👇-------- 简单概述 --------其中,MONAIAuto3DSeg扩展附带数十种针对特定临床用例的预训练分割模型,这些模型旨在快速运行,几分钟内即可在任何普通计算机上运行,无需 GPU,甚至在笔记本电脑上。

zzjlhlcd的博客 1698

EEVDF调度算法核心实现解析(一)

本文深入解析Linux kernel 6.6引入的EEVDF调度算法核心实现,详细讲解其基于虚拟截止时间(virtual deadline)的进程挑选机制和红黑树优化设计。通过代码实例展示算法如何平衡系统响应速度与公平性,特别适合需要优化Linux系统性能的开发者参考。

weixin_42522953的博客 478

Redis 事件机制 - AE 抽象层

Redis 事件机制 - AE 抽象层

m0_73096566的博客 1482

手把手教你用CANape调试XCP安全会话:DLL权限管理+四种加密模式实战演示

本文详细介绍了如何使用CANape调试XCP安全会话,重点解析了DLL权限管理的实现与配置方法,并通过实战演示了标定页切换、DAQ测量、STIM旁路和PGM刷写四种典型加密模式的操作流程与调试技巧,帮助工程师深入理解并掌握XCP安全访问机制。

smartcontract5的博客 562

【redis源码学习】事件机制

文章目录redis事件机制概述redis的事件循环器:aeEventLoopredis启动事件循环 redis事件机制概述 1、redis使用 IO 复用 实现网络通信。 2、在Linux环境下选用epoll模式。 redis的事件循环器:aeEventLoop acEventLoop 是 redis 的事件循环器,负责管理事件。 /* State of an event based program */ typedef struct aeEventLoop { int maxfd; //当前已注.

看,未来的博客 828

当AI写小说——一个被严重低估的工程化战场

摘要 AI小说创作远非简单的prompt生成,而是一项需要系统化工程管理的复杂任务。本文通过真实案例分析,揭示了当前AI写小说存在的五大核心难题:设定一致性难以维持、文风控制缺乏个性、爽感节奏难以把握、长篇记忆能力有限以及平台规则适配困难。研究显示,90%的失败案例源于错误的创作方法,主要表现为对话框式随机生成和模板式同质化创作。要实现高质量的AI小说创作,必须建立包括设定治理、记忆管理、文风校准等在内的全链路工程化体系,而非依赖单一prompt指令。只有采用系统化的人机协同方法,才能突破当前AI创作的质量

sellyogurt的博客 683

AI Agent开发第82课-手把手教你把paddleocr-vl做成mcp使其在DIFY里运行

本文详解如何将国产顶尖多模态 OCR 模型 PaddleOCR-VL 封装为标准 MCP(Model Calling Protocol)服务,并通过自研的 HTTP-based MCP Client 接入 Dify 1.10,实现 AI Agent 在对话中自动识别并调用 OCR 能力。该方案已在某头部保险公司真实业务流程中落地,使用了当前最先进的“Agentic AI 原生”设计思想。

打造全国最全的AI Agent开发知识领域的博客 1626

【Redis-6.0.8】事件循环器AE(下)

初始化客户端连接的read handler处理流程

Edidaughter的博客 623

看28377代码笔记

使用的是20MHz外部晶振,为取200MHz系统时钟,倍频设为20,分频设为2,这样sysclock = 20MHz * 20 / 2,正好200MHz。注意:SPISTE配置成普通的GPIO端口,是为了更好控制芯片,配置芯片的时序。FMULT_0:小数点位数,小数只能有0,0.25,0.5,0.75四个选择。XTAL_OSC:表示使用外部晶振。GPIO_MUX_CPU1:复用类型。GPIO_MUX_CPU1:复用类型。PLLCLK_BY_2:表示2分频。IMULT_20:表示倍频数。

小柚子的博客 807

为什么redis取出来是null_Redis事件循环器(AE)实现剖析

Redis事件循环器(AE)实现剖析​www.cyhone.comRedis作为一个单线程高性能的内存缓存Server而被人熟知。作为一个典型的Reactor式网络应用,Redis能够达到如此高的性能,必然要依靠足够可靠的事件循环库。 Redis内置了一个高性能事件循环器,叫做AE。其定义和实现可以在ae*.h/cpp这些文件中找到。AE本身就是Redis的一部分,所以整体设计原则就是够用就行。也...

weixin_39683598的博客 309

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Redis 事件驱动模型:ae 事件循环、文件事件与时间事件调度

本文深入解析 Redis 的事件驱动模型,详细介绍 ae 事件循环原理、文件事件调度机制和时间事件处理流程,并通过最小示例展示其核心实现。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 4235

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Redis源码阅读指南:揭开启动流程与事件循环的神秘面纱

本文深入解析Redis从main函数到事件循环启动的完整流程,揭示高性能服务器的初始化秘密。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 4194

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Redis 事件驱动框架(ae.c_ae.h)深度解析

本文深入解析Redis事件驱动框架ae.c/ae.h的核心机制。文章首先明确ae模块解决单线程处理高并发连接的关键问题,随后拆解其三大核心组件:事件循环(总调度台)、文件事件(客户端请求)和时间事件(定时任务)。通过超市收银员的生动类比,详细说明事件循环的工作流程,包括监听文件事件、处理定时任务的智能调度过程。文章还揭示了Redis单线程高性能的奥秘——基于epoll/kqueue的IO多路复用技术,并指导读者如何快速定位源码关键部分。最后总结ae模块作为Redis多任务调度中心的核心作用,展示其如何通过精

01空间站,就是分享技术,知识互动 572

gh_mirrors/re/redis-3.0-annotated源码解析:Redis服务器的事件循环与回调机制

你是否好奇Redis为何能处理每秒数十万的请求?为何在高并发场景下依然保持低延迟?答案就藏在它的事件循环(Event Loop)机制中。Redis作为一款高性能的内存数据库,其单线程模型下的事件驱动架构是实现高吞吐量的关键。本文将深入解析[gh_mirrors/re/redis-3.0-annotated](https://link.gitcode.com/i/0983433d570763a561...

gitblog_00921的博客 954

【Redis-6.0.8】合集

【Redis-6.0.8】Redis集群源码-集群初始化

Edidaughter的博客 1214

Redis 事件循环(Event Loop)

Redis 采用的事件驱动模型,其事件循环核心逻辑在ae.c中实现。本文基于 Linux 下的 epoll 事件驱动模型。

djefe的博客 1167

Redis AE异步事件库实例分析

Redis AE异步事件库实例分析 https://www.jianshu.com/p/da092472080e Redis使用了一个称为“A simple event-driven programming library”的自制异步事件库(以下简称“AE”)。整个事件库的代码量少于1k行,是个优秀的C异步事件库学习材料。 源码结构 版本 Redis 4.0.8 r...

TuxedoLinux的博客 663

Redis源码探究系列—事件循环模型全景解析(ae.c)

Redis是单线程的,却能处理大量并发连接,核心在于它的事件循环机制。ae.c是Redis自己实现的一个轻量级事件库,支持I/O多路复用和定时器。这篇文章拆解它的实现原理。

Deepincode的博客 538

18.事件模型——ae_epoll 事件循环源码走读,命令执行 pipeline 图解

源码位置:src/ae_epoll.c(Redis 7.2 分支,commit 7b0c25c)18. 事件模型——ae_epoll 事件循环源码走读,命令执行 pipeline 图解。(基于 优快云 前文 152161640 的上下文,继续深入)下面把“客户端发来一条 SET” 时,Redis 主线程。所走的路径画成一张泳道。

IT老农民的博客 920

Redis源码深度剖析:事件循环(Event Loop)如何成就其单线程高性能神话?

Redis源码深度剖析:事件循环(Event Loop)如何成就其单线程高性能神话?Redis的事件循环机制是其高性能的基石。它通过单线程事件循环 + I/O多路复用的设计,将CPU时间几乎完全用在刀刃上——内存数据操作,巧妙地规避了多线程的并发复杂性,并实现了海量连接的管理。

夜雨的博客 864

Redis事件驱动架构详解:annotated_redis_source中ae模块的完整分析

Redis作为高性能的内存数据库,其核心优势之一在于采用了高效的事件驱动架构。在annotated_redis_source项目中,这一架构的实现集中体现在`ae`模块(Ae Event Loop)中。本文将深入剖析Redis事件驱动模型的工作原理,带你理解`ae`模块如何高效处理网络请求和定时任务,以及它如何支撑Redis的高并发性能。 ## 什么是事件驱动架构? 事件驱动架构是一种以事件为

gitblog_00937的博客 444

Redis6.0的多IO线程

Redis6.0的多IO线程 通过上篇文章的学习,我们知道 Redis server 启动后的进程会以单线程的方式,执行客户端请求解析和处理工作。但是,Redis server 也会通过 bioInit 函数启动三个后台线程,来处理后台任务。也就是说,Redis 不再让主线程执行一些耗时操作,比如同步写、删除等,而是交给后台线程异步完成,从而避免了对主线程的阻塞。 实际上,在 2020 年 5 月推出的 Redis 6.0 版本中,Redis 在执行模型中还进一步使用了多线程来处理 IO 任务,这样设计的目

子冉冰清的博客 1359
上一篇: 【Redis-6.0.8】Redis中的定时任务
下一篇: 【Redis-6.0.8】 server对象的各个成员的含义
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值