1、事件dispatcher

      首先,我们得将事件分发器dispatcher抽象出来,dispatcher的主要作用实现socket的监听,注册。抽象的event_dispatcher结构(类)是利用回调函数进行设计的如下所示。我们可以基于这个event_dispatcher去实现基于select,poll或者epoll的模型,这样的思路也是Netty这种框架的基本思路。

struct event_dispatcher {   /*对应实现*/const char *name;   /*初始化函数 */void *(*init)(struct event_loop * eventLoop);   /*通知dispatcher新增一个channel事件*/int (*add)(struct event_loop* eventLoop, struct channel* channel);   /*通知dispatcher删除一个channel事件*/int (*del)(struct event_loop* eventLoop,struct channel* channel);  /*通知dispatcher更新channel对应的事件*/int (*update)(struct event_loop* eventLoop,struct channel* channel);  /*实现事件分发*/int (*dispatch)(struct event_loop* eventLoop,struct timeval*);  /*清除数据 */void (*clear)(struct event_loop * eventLoop);};
      需要注意的是这里面的event_loop是代表一个封装后的线程,这个下面介绍。假设我们这里想实现基于epoll的事件分发机制,这里可以实现如下:
static void *epoll_init(struct event_loop*);static int epoll_add(struct event_loop*, struct channel*);static int epoll_del(struct event_loop*, struct channel*);static int epoll_update(struct event_loop*, struct channel*);static int epoll_dispatch(struct event_loop*, struct timeval*);static void epoll_clear(struct event_loop*);const struct event_dispatcher epoll_dispatcher = {        "epoll",        epoll_init,        epoll_add,        epoll_del,        epoll_update,        epoll_dispatch,        epoll_clear,};
    也就是说,我们最终accept或者accept得到的socket都会委托给epoll_dispatcher里面这几个回调方法进行注册监听读写事件,从而完成数据的读写交互。
2、main-sub线程初始化
      接下来,我们先看一下整体的处理流程,其中main-event_loop线程负责accept新连接,然后将新连接封装成channel,并从线程池(eventloopGroup)中根据Round Robin算法选择一个event_loop进行处理IO读写事件,整体的流程如下所示。

       这里首先要解决的第一个问题是,main-eventloop如何等子线程初始化完成,也就是说只有将子线程初始化完成才能将main-eventloop得到的连接传递给子线程进行处理,注册到子线程的dispatcher上进行读写事件的监听。
  
      我们这里采用的方式是mutex和condition,如下所示,我们先初始化完成了main-eventloop线程,然后通过main-eventloop线程去创建子线程并初始化,看到下面的for循环了吗?其中event_loop_thread_init函数用于创建该eventloop线程中的cmutx和cond,调用的函数分别是pthread_mutex_init和pthread_cond_init。

void thread_pool_start(struct thread_pool *threadPool){    assert(!threadPool->started);    assertInSameThread(threadPool->mainLoop);    threadPool->started = 1;    if (threadPool->thread_number <= 0) {        return;    }    //创建N个 eventloopThread线程    threadPool->eventLoopThreads = malloc(threadPool->thread_number*sizeof(struct event_loop_thread));    for (int i = 0; i < threadPool->thread_number; ++i) {        event_loop_thread_init(&threadPool->eventLoopThreads[i],i);        event_loop_thread_start(&threadPool->eventLoopThreads[i]);    }}
      接着,我们进入到event_loop_thread_start函数看看,首先调用pthread_create函数创建一个线程,线程的入库函数就是event_loop_run方法,也就是一个死循环,不断的监听IO事件,处理事件。
//由主线程调用,初始化一个子线程,并且让子线程开始运行event_loopstruct event_loop *event_loop_thread_start(struct event_loop_thread *eventLoopThread) {    pthread_create(&eventLoopThread->thread_tid, NULL, &event_loop_thread_run, eventLoopThread);    assert(pthread_mutex_lock(&eventLoopThread->mutex) == 0);     while (eventLoopThread->eventLoop == NULL) {        assert(pthread_cond_wait(&eventLoopThread->cond, &eventLoopThread->mutex) == 0);    }    assert(pthread_mutex_unlock(&eventLoopThread->mutex) == 0);    return eventLoopThread->eventLoop;}
void *event_loop_thread_run(void *arg) {    struct event_loop_thread *eventLoopThread = (struct event_loop_thread *) arg;    pthread_mutex_lock(&eventLoopThread->mutex);    // 初始化化event loop,之后通知主线程    eventLoopThread->eventLoop = event_loop_init_with_name(eventLoopThread->thread_name);    pthread_cond_signal(&eventLoopThread->cond);    pthread_mutex_unlock(&eventLoopThread->mutex);    //子线程event loop run    event_loop_run(eventLoopThread->eventLoop);}
     从上面的代码可以看到,event_loop_thread_run 一上来也是进行了加锁,之后初始化 event_loop 对象,当初始化完成之后,调用了 pthread_cond_signal 函数来通知此时阻塞在 pthread_cond_wait 上的主线程。这样,主线程就会从 wait 中苏醒,代码得以往下执行。子线程本身也通过调用 event_loop_run 进入了一个无限循环的事件分发执行体中,等待子线程 reator 上注册过的事件发生。


      这种main-Eventloop初始化子eventloop的方法,总的来说就是用子线程中的eventloop是否为空,如果为空说明子线程没有初始化,则main eventloop调用pthread_cond_wait出让CPU。子线程一进来就加锁,然后进行初始化。等子线程初始化完成,则调用pthread_cond_signal唤醒main eventloop继续执行。除了这种方式,还有其他eventfd方式也可以进行主线程和子线程初始化,这里不做介绍了。
3、round-robin算法

     round-robin算法是一种负载均衡算法,在负载均衡算法中是最简单和最容易理解的一种算法,其核心思路就是按顺序进行分配。在我们这里也是按照子线程的顺序轮流进行获取eventloop,那么怎么获取呢?

struct event_loop *thread_pool_get_loop(struct thread_pool* threadPool){    assert(threadPool->started);    assertInSameThread(threadPool->mainLoop);    //优先选择当前主线程    struct event_loop *selected = threadPool->mainLoop;    //从线程池中按照顺序挑选出一个线程    if (threadPool->thread_number > 0) {        selected=threadPool->eventLoopThreads[threadPool->position].eventLoop;        if (++threadPool->position >= threadPool->thread_number) {            threadPool->position = 0;        }    }    return selected;}

     这里的threadPool->thread_number参数是初始化子线程传递进去的,是一个固定值。我们用一个position变量代表取到了第几个线程。当position达到thread_number的时候,则将position清零,也就是从头开始下一轮分配线程eventloop。


      这里也可以看出,如果没有子线程,也就是单Reactor模式,我们也不会进入这个选择线程的for循环了,只能选择main eventloop thread进行处理IO事件了。


4、小结
      到这里,我们知道了主线程和子线程初始化的顺序和过程,现在初始化好了主线程和子线程,那么下一篇我们将看看主线程如何处理新连接,又如何注册到子线程。子线程收到读写事件,如何处理读写事件。