Memcached源码分析--线程模型(二)

原文: http://www.iteye.com/topic/344172

下面看看thread_init是怎样启动所有workers线程的,看一下thread_init里的核心代码 

  1. void thread_init(int nthreads, struct event_base *main_base) {   
  2.  //。。。省略   
  3.    threads = malloc(sizeof(LIBEVENT_THREAD) * nthreads);   
  4.     if (! threads) {   
  5.         perror("Can't allocate thread descriptors");   
  6.         exit(1);   
  7.     }   
  8.    
  9.     threads[0].base = main_base;   
  10.     threads[0].thread_id = pthread_self();   
  11.    
  12.     for (i = 0; i < nthreads; i++) {   
  13.         int fds[2];   
  14.         if (pipe(fds)) {   
  15.             perror("Can't create notify pipe");   
  16.             exit(1);   
  17.         }   
  18.    
  19.         threads[i].notify_receive_fd = fds[0];   
  20.         threads[i].notify_send_fd = fds[1];   
  21.    
  22.     setup_thread(&threads[i]);   
  23.     }   
  24.    
  25.     /* Create threads after we've done all the libevent setup. */   
  26.     for (i = 1; i < nthreads; i++) {   
  27.         create_worker(worker_libevent, &threads[i]);   
  28.     }   
  29. }   

threads的声明是这样的 

static LIBEVENT_THREAD *threads; 

thread_init首先malloc线程的空间,然后第一个threads作为主线程,其余都是workers线程 
然后为每个线程创建一个pipe,这个pipe被用来作为主线程通知workers线程有新的连接到达 

看下setup_thread 

  1. static void setup_thread(LIBEVENT_THREAD *me) {   
  2.     if (! me->base) {   
  3.         me->base = event_init();   
  4.         if (! me->base) {   
  5.             fprintf(stderr, "Can't allocate event base\n");   
  6.             exit(1);   
  7.         }   
  8.     }   
  9.    
  10.     /* Listen for notifications from other threads */   
  11.     event_set(&me->notify_event, me->notify_receive_fd,   
  12.               EV_READ | EV_PERSIST, thread_libevent_process, me);   
  13.     event_base_set(me->base, &me->notify_event);   
  14.    
  15.     if (event_add(&me->notify_event, 0) == -1) {   
  16.         fprintf(stderr, "Can't monitor libevent notify pipe\n");   
  17.         exit(1);   
  18.     }   
  19.    
  20.     cq_init(&me->new_conn_queue);   
  21. }   

setup_thread主要是创建所有workers线程的libevent实例(主线程的libevent实例在main函数中已经建立) 


由于之前 threads[0].base = main_base;所以第一个线程(主线程)在这里不会执行event_init() 
然后就是注册所有workers线程的管道读端的libevent的读事件,等待主线程的通知 
最后在该方法里将所有的workers的CQ初始化了 

create_worker实际上就是真正启动了线程,pthread_create调用worker_libevent方法,该方法执行 
event_base_loop启动该线程的libevent 

这里我们需要记住每个workers线程目前只在自己线程的管道的读端有数据时可读时触发,并调用 
thread_libevent_process方法 

看一下这个函数 

  1. static void thread_libevent_process(int fd, short which, void *arg){   
  2.     LIBEVENT_THREAD *me = arg;   
  3.     CQ_ITEM *item;   
  4.     char buf[1];   
  5.    
  6.     if (read(fd, buf, 1) != 1)   
  7.         if (settings.verbose > 0)   
  8.             fprintf(stderr, "Can't read from libevent pipe\n");   
  9.    
  10.     item = cq_peek(&me->new_conn_queue);   
  11.    
  12.     if (NULL != item) {   
  13.         conn *c = conn_new(item->sfd, item->init_state, item->event_flags,   
  14.                            item->read_buffer_size, item->is_udp, me->base);   
  15.         。。。//省略   
  16.     }   
  17. }   

函数参数的fd是这个线程的管道读端的描述符 

首先将管道的1个字节通知信号读出(这是必须的,在水平触发模式下如果不处理该事件,则会被循环通知,知道事件被处理) 

cq_peek是从该线程的CQ队列中取队列头的一个CQ_ITEM,这个CQ_ITEM是被主线程丢到这个队列里的,item->sfd是已经建立的连接的描述符,通过conn_new函数为该描述符注册libevent的读事件,me->base是代表自己的一个线程结构体,就是说对该描述符的事件处理交给当前这个workers线程处理,conn_new方法的最重要的内容是: 

  1. conn *conn_new(const int sfd, const int init_state, const int event_flags,   
  2.                 const int read_buffer_size, const bool is_udp, struct event_base *base) {   
  3.     ...   
  4.         event_set(&c->event, sfd, event_flags, event_handler, (void *)c);   
  5.         event_base_set(base, &c->event);   
  6.         c->ev_flags = event_flags;   
  7.         if (event_add(&c->event, 0) == -1) {   
  8.         if (conn_add_to_freelist(c)) {   
  9.             conn_free(c);   
  10.         }   
  11.         perror("event_add");   
  12.         return NULL;   
  13.         }   
  14.     ...   
  15. }   

可以看到新的连接被注册了一个事件(实际是EV_READ|EV_PERSIST),由当前线程处理(因为这里的event_base是该workers线程自己的) 

当该连接有可读数据时会回调event_handler函数,实际上event_handler里主要是调用memcached的核心方法drive_machine 

最后看看主线程是如何通知workers线程处理新连接的,主线程的libevent注册的是监听socket描述字的可读事件,就是说 
当有建立连接请求时,主线程会处理,回调的函数是也是event_handler(因为实际上主线程也是通过conn_new初始化的监听socket 的libevent可读事件) 

Memcached源码分析--线程模型(一)

Memcached源码分析--线程模型(三)

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