原文: http://www.iteye.com/topic/344172
下面看看thread_init是怎樣啓動所有workers線程的,看一下thread_init裏的核心代碼
- void thread_init(int nthreads, struct event_base *main_base) {
- //。。。省略
- threads = malloc(sizeof(LIBEVENT_THREAD) * nthreads);
- if (! threads) {
- perror("Can't allocate thread descriptors");
- exit(1);
- }
- threads[0].base = main_base;
- threads[0].thread_id = pthread_self();
- for (i = 0; i < nthreads; i++) {
- int fds[2];
- if (pipe(fds)) {
- perror("Can't create notify pipe");
- exit(1);
- }
- threads[i].notify_receive_fd = fds[0];
- threads[i].notify_send_fd = fds[1];
- setup_thread(&threads[i]);
- }
- /* Create threads after we've done all the libevent setup. */
- for (i = 1; i < nthreads; i++) {
- create_worker(worker_libevent, &threads[i]);
- }
- }
threads的聲明是這樣的
static LIBEVENT_THREAD *threads;
thread_init首先malloc線程的空間,然後第一個threads作爲主線程,其餘都是workers線程
然後爲每個線程創建一個pipe,這個pipe被用來作爲主線程通知workers線程有新的連接到達
看下setup_thread
- static void setup_thread(LIBEVENT_THREAD *me) {
- if (! me->base) {
- me->base = event_init();
- if (! me->base) {
- fprintf(stderr, "Can't allocate event base\n");
- exit(1);
- }
- }
- /* Listen for notifications from other threads */
- event_set(&me->notify_event, me->notify_receive_fd,
- EV_READ | EV_PERSIST, thread_libevent_process, me);
- event_base_set(me->base, &me->notify_event);
- if (event_add(&me->notify_event, 0) == -1) {
- fprintf(stderr, "Can't monitor libevent notify pipe\n");
- exit(1);
- }
- cq_init(&me->new_conn_queue);
- }
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方法
看一下這個函數
- static void thread_libevent_process(int fd, short which, void *arg){
- LIBEVENT_THREAD *me = arg;
- CQ_ITEM *item;
- char buf[1];
- if (read(fd, buf, 1) != 1)
- if (settings.verbose > 0)
- fprintf(stderr, "Can't read from libevent pipe\n");
- item = cq_peek(&me->new_conn_queue);
- if (NULL != item) {
- conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
- item->read_buffer_size, item->is_udp, me->base);
- 。。。//省略
- }
- }
函數參數的fd是這個線程的管道讀端的描述符
首先將管道的1個字節通知信號讀出(這是必須的,在水平觸發模式下如果不處理該事件,則會被循環通知,知道事件被處理)
cq_peek是從該線程的CQ隊列中取隊列頭的一個CQ_ITEM,這個CQ_ITEM是被主線程丟到這個隊列裏的,item->sfd是已經建立的連接的描述符,通過conn_new函數爲該描述符註冊libevent的讀事件,me->base是代表自己的一個線程結構體,就是說對該描述符的事件處理交給當前這個workers線程處理,conn_new方法的最重要的內容是:
- conn *conn_new(const int sfd, const int init_state, const int event_flags,
- const int read_buffer_size, const bool is_udp, struct event_base *base) {
- ...
- event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
- event_base_set(base, &c->event);
- c->ev_flags = event_flags;
- if (event_add(&c->event, 0) == -1) {
- if (conn_add_to_freelist(c)) {
- conn_free(c);
- }
- perror("event_add");
- return NULL;
- }
- ...
- }
可以看到新的連接被註冊了一個事件(實際是EV_READ|EV_PERSIST),由當前線程處理(因爲這裏的event_base是該workers線程自己的)
當該連接有可讀數據時會回調event_handler函數,實際上event_handler裏主要是調用memcached的核心方法drive_machine
最後看看主線程是如何通知workers線程處理新連接的,主線程的libevent註冊的是監聽socket描述字的可讀事件,就是說
當有建立連接請求時,主線程會處理,回調的函數是也是event_handler(因爲實際上主線程也是通過conn_new初始化的監聽socket 的libevent可讀事件)