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源碼分析--線程模型(三)

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