Libevent源码分析-----evthread_notify_base通知主线程



        一般来说,是主线程执行event_base_dispatch函数。本文也是如此,如无特别说明,event_base_dispatch函数是由主线程执行的。


notify的理由:

        本文要说明的问题是,当主线程在执行event_base_dispatch进入多路IO复用函数时,会处于休眠状态,休眠前解锁。此时,其他线程可能想往event_base添加一个event,这个event可能是一般的IO event也可能是超时event。无论哪个,都需要及时告知主线程,有新的event要加进来。要实现这种功能就需要Libevent提供一种机制来提供唤醒主线程。


工作原理:

        Libevent提供的唤醒主线程机制也是挺简单的,其原理和《信号event的处理》一文中提到的方法是一样的。提供一个内部的IO  event,专门用于唤醒主线程。当其他线程有event要add进来时,就往这个内部的IO event写入一个字节。此时,主线程在dispatch时,就能检测到可读,也就醒来了。这就完成了通知。这过程和Libevent处理信号event是一样的。


相关结构体:

        下面看一下Libevent的实现代码。同信号处理一样,先来看一下event_base提供了什么成员。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct event_base {  
  2.     …  
  3.     //event_base是否处于通知的未决状态。即次线程已经通知了,但主线程还没处理这个通知  
  4.     int is_notify_pending;  
  5.     evutil_socket_t th_notify_fd[2]; //通信管道  
  6.     struct event th_notify;//用于监听th_notify_fd的读端  
  7.     //有两个可供选择的通知函数,指向其中一个通知函数  
  8.     int (*th_notify_fn)(struct event_base *base);  
  9. };  


创建通知event并将之加入到event_base:

        现在看Libevent怎么创建通信通道,以及怎么和event相关联。在event_base_new_with_config(event_base_new会调用该函数)里面会调用evthread_make_base_notifiable函数,使得libevent变成可通知的。只有在已经支持多线程的情况下才会调用evthread_make_base_notifiable函数的。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. int  
  3. evthread_make_base_notifiable(struct event_base *base)  
  4. {  
  5.     //默认event回调函数和默认的通知函数  
  6.     void (*cb)(evutil_socket_t, shortvoid *) = evthread_notify_drain_default;  
  7.     int (*notify)(struct event_base *) = evthread_notify_base_default;  
  8.   
  9.     /* XXXX grab the lock here? */  
  10.     if (!base)  
  11.         return -1;  
  12.   
  13.     //th_notify_fd[0]被初始化为-1,如果>=0,就说明已经被设置过了  
  14.     if (base->th_notify_fd[0] >= 0)  
  15.         return 0;  
  16.   
  17. #if defined(_EVENT_HAVE_EVENTFD) && defined(_EVENT_HAVE_SYS_EVENTFD_H)  
  18. #ifndef EFD_CLOEXEC  
  19.   
  20. #define EFD_CLOEXEC 0  
  21. #endif  
  22.     //Libevent优先使用eventfd,但eventfd的通信机制和其他的不一样。所以  
  23.     //要专门为eventfd创建通知函数和event回调函数  
  24.     base->th_notify_fd[0] = eventfd(0, EFD_CLOEXEC);  
  25.     if (base->th_notify_fd[0] >= 0) {  
  26.         evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  27.         notify = evthread_notify_base_eventfd;  
  28.         cb = evthread_notify_drain_eventfd;  
  29.     }  
  30. #endif  
  31. #if defined(_EVENT_HAVE_PIPE)  
  32.     //<0,说明之前的通知方式没有用上  
  33.     if (base->th_notify_fd[0] < 0) {  
  34.         //有些多路IO复用函数并不支持文件描述符。如果不支持,那么就不能使用这种  
  35.         //通知方式。有关这个的讨论.查看http://blog.csdn.net/luotuo44/article/details/38443569  
  36.         if ((base->evsel->features & EV_FEATURE_FDS)) {  
  37.             if (pipe(base->th_notify_fd) < 0) {  
  38.                 event_warn("%s: pipe", __func__);  
  39.             } else {  
  40.                 evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  41.                 evutil_make_socket_closeonexec(base->th_notify_fd[1]);  
  42.             }  
  43.         }  
  44.     }  
  45. #endif  
  46.   
  47. #ifdef WIN32  
  48. #define LOCAL_SOCKETPAIR_AF AF_INET  
  49. #else  
  50. #define LOCAL_SOCKETPAIR_AF AF_UNIX  
  51. #endif  
  52.   
  53.     if (base->th_notify_fd[0] < 0) {  
  54.         if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0,  
  55.             base->th_notify_fd) == -1) {  
  56.             event_sock_warn(-1, "%s: socketpair", __func__);  
  57.             return (-1);  
  58.         } else {  
  59.             evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  60.             evutil_make_socket_closeonexec(base->th_notify_fd[1]);  
  61.         }  
  62.     }  
  63.   
  64.     //无论哪种通信机制,都要使得读端不能阻塞  
  65.     evutil_make_socket_nonblocking(base->th_notify_fd[0]);  
  66.   
  67.     //设置回调函数  
  68.     base->th_notify_fn = notify;  
  69.   
  70.     //同样为了让写端不阻塞。虽然,如果同时出现大量需要notify的操作,会塞满通信通道。  
  71.     //本次的notify会没有写入到通信通道中(已经变成非阻塞了)。但这无所谓,因为目的是  
  72.     //唤醒主线程,通信通道有数据就肯定能唤醒。  
  73.     if (base->th_notify_fd[1] > 0)  
  74.         evutil_make_socket_nonblocking(base->th_notify_fd[1]);  
  75.   
  76.     //该函数的作用等同于event_new。实际上event_new内部也是调用event_assign函数完成工作的  
  77.     //函数cb作为这个event的回调函数  
  78.     event_assign(&base->th_notify, base, base->th_notify_fd[0],  
  79.                  EV_READ|EV_PERSIST, cb, base);  
  80.   
  81.     //标明是内部使用的  
  82.     base->th_notify.ev_flags |= EVLIST_INTERNAL;  
  83.     event_priority_set(&base->th_notify, 0); //最高优先级  
  84.   
  85.     return event_add(&base->th_notify, NULL);//加入到event_base中。  
  86. }  

        上面代码展示了,Libevent会从eventfd、pipe和socketpair中选择一种通信方式。由于有三种通信通道可供选择,下文为了方便叙述,就假定它选定的是pipe。

        上面代码的工作过程和普通的Libevent例子程序差不多,首先创建一个文件描述符fd,然后用这个fd创建一个event,最后添加到event_base中。


唤醒流程:

        现在沿着这个内部的event的工作流程走一遍。


启动notify:

        首先往event写入一个字节,开启一切。由于这个event是内部的,用户是接触不到的。所以只能依靠Libevent提供的函数。当然这个函数也不会开放给用户,它只是供Libevent内部使用。

        现在来看Libevent内部的需要。在event_add_internal函数中需要通知主线程,在该函数的最后面会调用evthread_notify_base。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. evthread_notify_base(struct event_base *base)  
  4. {  
  5.     //确保已经加锁了  
  6.     EVENT_BASE_ASSERT_LOCKED(base);  
  7.     if (!base->th_notify_fn)  
  8.         return -1;  
  9.   
  10.     //写入一个字节,就能使event_base被唤醒。  
  11.     //如果处于未决状态,就必要写多一个字节  
  12.     if (base->is_notify_pending)  
  13.         return 0;  
  14.   
  15.     //通知处于未决状态,当event_base醒过来就变成已决的了。  
  16.     base->is_notify_pending = 1;  
  17.     return base->th_notify_fn(base);  
  18. }  

        在evthread_notify_base中,会调用th_notify_fn函数指针。这个指针是在前面的evthread_make_base_notifiable函数中被赋值的。这里以evthread_notify_base_default作为例子。这个evthread_notify_base_default完成实际的通知操作。


激活内部event:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件。  
  2. static int  
  3. evthread_notify_base_default(struct event_base *base)  
  4. {  
  5.     char buf[1];  
  6.     int r;  
  7.     buf[0] = (char) 0;  
  8.     //通知一下,用来唤醒。写一个字节足矣  
  9. #ifdef WIN32  
  10.     r = send(base->th_notify_fd[1], buf, 1, 0);  
  11. #else  
  12.     r = write(base->th_notify_fd[1], buf, 1);  
  13. #endif  
  14.     //即使errno 等于 EAGAIN也无所谓,因为这是由于通信通道已经塞满了  
  15.     //这已经能唤醒主线程了。没必要一定要再写入一个字节  
  16.     return (r < 0 && errno != EAGAIN) ? -1 : 0;  
  17. }  

        从上面两个函数看到,其实通知也是蛮简单的。只是往管道里面写入一个字节。当然这已经能使得event_base检测到管道可读,从而实现唤醒event_base。

        往管道写入一个字节,event_base就会被唤醒,然后调用这个管道对应event的回调函数。当然,在event_base醒来的时候,还能看到其他东西。这也是Libevent提供唤醒功能的原因。


        现在看一下这个唤醒event的回调函数,也是看默认的那个。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c  
  2. static void  
  3. evthread_notify_drain_default(evutil_socket_t fd, short what, void *arg)  
  4. {  
  5.     unsigned char buf[1024];  
  6.     struct event_base *base = arg;  
  7.   
  8.     //读完fd的所有数据,免得再次被唤醒  
  9. #ifdef WIN32  
  10.     while (recv(fd, (char*)buf, sizeof(buf), 0) > 0)  
  11.         ;  
  12. #else  
  13.     while (read(fd, (char*)buf, sizeof(buf)) > 0)  
  14.         ;  
  15. #endif  
  16.   
  17.     EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  18.     //修改之,使得其不再是未决的了。当然这也能让其他线程可以再次唤醒值。参看evthread_notify_base函数  
  19.     base->is_notify_pending = 0;  
  20.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  21. }  

        这个函数也比较简单,也就是读取完管道里的所有数据,免得被多路IO复用函数检测到管道可读,而再次被唤醒。

      

        上面的流程就完成了Libevent的通知唤醒主线程的功能,思路还是蛮清晰的。实现起来也是很简单。



注意事项:

        有一点要注意:要让Libevent支持这种可通知机制,就必须让Libevent使用多线程,即在代码的一开始调用evthread_use_pthreads()或者evthread_use_windows_threads()。虽然用户可以手动调用函数evthread_make_base_notifiable。但实际上是不能实现通知功能的。分析如下:

        Libevent代码中是通过调用函数evthread_notify_base来通知的。但这个函数都是在一个if语句中调用的。判断的条件为是否需要通知。If成立的条件中肯定会&&上一个EVBASE_NEED_NOTIFY(base)。比如在event_add_internal函数中的为:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))  
  2.         evthread_notify_base(base);  

        notify是根据函数中的判断而来,而EVBASE_NEED_NOTIFY这个宏定义的作用是判断当前的线程是否不等于主线程(即为event_base执行event_base_dispatch函数的线程)。它是一个条件宏。其中一个实现为:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define EVBASE_NEED_NOTIFY(base)             \  
  2.     (_evthread_id_fn != NULL &&          \  
  3.         (base)->running_loop &&           \  
  4.         (base)->th_owner_id != _evthread_id_fn())  
  5.   
  6. #define EVTHREAD_GET_ID() \  
  7.     (_evthread_id_fn ? _evthread_id_fn() : 1)  

        event_base结构体中th_owner_id变量指明当前为event_base执行event_base_dispatch函数的是哪个线程。在event_base_loop函数中用宏EVTHREAD_GET_ID()赋值。

        如果一开始没有调用evthread_use_pthreads或者evthread_use_windows_threads,那么全局变量evthread_id_fn就为NULL。也就不能获取线程的ID了。EVBASE_NEED_NOTIFY宏也只会返回0,使得不能调用evthread_notify_base函数。关于线程这部分的分析,可以参考《多线程、锁、条件变量(一)》和《多线程、锁、条件变量(二)》。


        下面的用一个例子验证。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<event.h>  
  2. #include<stdio.h>  
  3. #include<unistd.h>  
  4. #include<thread.h>  
  5.   
  6. #include<pthread.h> //Linux thread  
  7.   
  8. struct event_base *base = NULL;  
  9.   
  10.   
  11. void pipe_cb(int fd, short events, void *arg)  
  12. {  
  13.     printf("in the cmd_cb\n");  
  14. }  
  15.   
  16. void timeout_cb(int fd, short events, void *arg)  
  17. {  
  18.     printf("in the timeout_cb\n");  
  19. }  
  20.   
  21. void* thread_fn(void *arg)  
  22. {  
  23.     char ch;  
  24.     scanf("%c", &ch); //just for wait  
  25.   
  26.     struct event *ev = event_new(base, -1, EV_TIMEOUT | EV_PERSIST,  
  27.                                  timeout_cb, NULL);  
  28.   
  29.     struct timeval tv = {2, 0};  
  30.     event_add(ev, &tv);  
  31. }  
  32.   
  33. int main(int argc, char ** argv)  
  34. {  
  35.     if( argc >= 2 && argv[1][0] == 'y')  
  36.         evthread_use_pthreads();  
  37.   
  38.     base = event_base_new();  
  39.     evthread_make_base_notifiable(base);  
  40.   
  41.     int pipe_fd[2];  
  42.     pipe(pipe_fd);  
  43.   
  44.     struct event *ev = event_new(base, pipe_fd[0],  
  45.                                  EV_READ | EV_PERSIST, pipe_cb, NULL);  
  46.   
  47.     event_add(ev, NULL);  
  48.   
  49.     pthread_t thread;  
  50.     pthread_create(&thread, NULL, thread_fn, NULL);  
  51.   
  52.       
  53.     event_base_dispatch(base);  
  54.   
  55.     return 0;  
  56. }  

        如果次线程的event被add到event_base中,那么每2秒timeout_cb函数就会被调用一次。如果没有被add的话,就永远等待下去,没有任何输出。


        一般来说,是主线程执行event_base_dispatch函数。本文也是如此,如无特别说明,event_base_dispatch函数是由主线程执行的。


notify的理由:

        本文要说明的问题是,当主线程在执行event_base_dispatch进入多路IO复用函数时,会处于休眠状态,休眠前解锁。此时,其他线程可能想往event_base添加一个event,这个event可能是一般的IO event也可能是超时event。无论哪个,都需要及时告知主线程,有新的event要加进来。要实现这种功能就需要Libevent提供一种机制来提供唤醒主线程。


工作原理:

        Libevent提供的唤醒主线程机制也是挺简单的,其原理和《信号event的处理》一文中提到的方法是一样的。提供一个内部的IO  event,专门用于唤醒主线程。当其他线程有event要add进来时,就往这个内部的IO event写入一个字节。此时,主线程在dispatch时,就能检测到可读,也就醒来了。这就完成了通知。这过程和Libevent处理信号event是一样的。


相关结构体:

        下面看一下Libevent的实现代码。同信号处理一样,先来看一下event_base提供了什么成员。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. struct event_base {  
  2.     …  
  3.     //event_base是否处于通知的未决状态。即次线程已经通知了,但主线程还没处理这个通知  
  4.     int is_notify_pending;  
  5.     evutil_socket_t th_notify_fd[2]; //通信管道  
  6.     struct event th_notify;//用于监听th_notify_fd的读端  
  7.     //有两个可供选择的通知函数,指向其中一个通知函数  
  8.     int (*th_notify_fn)(struct event_base *base);  
  9. };  


创建通知event并将之加入到event_base:

        现在看Libevent怎么创建通信通道,以及怎么和event相关联。在event_base_new_with_config(event_base_new会调用该函数)里面会调用evthread_make_base_notifiable函数,使得libevent变成可通知的。只有在已经支持多线程的情况下才会调用evthread_make_base_notifiable函数的。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. int  
  3. evthread_make_base_notifiable(struct event_base *base)  
  4. {  
  5.     //默认event回调函数和默认的通知函数  
  6.     void (*cb)(evutil_socket_t, shortvoid *) = evthread_notify_drain_default;  
  7.     int (*notify)(struct event_base *) = evthread_notify_base_default;  
  8.   
  9.     /* XXXX grab the lock here? */  
  10.     if (!base)  
  11.         return -1;  
  12.   
  13.     //th_notify_fd[0]被初始化为-1,如果>=0,就说明已经被设置过了  
  14.     if (base->th_notify_fd[0] >= 0)  
  15.         return 0;  
  16.   
  17. #if defined(_EVENT_HAVE_EVENTFD) && defined(_EVENT_HAVE_SYS_EVENTFD_H)  
  18. #ifndef EFD_CLOEXEC  
  19.   
  20. #define EFD_CLOEXEC 0  
  21. #endif  
  22.     //Libevent优先使用eventfd,但eventfd的通信机制和其他的不一样。所以  
  23.     //要专门为eventfd创建通知函数和event回调函数  
  24.     base->th_notify_fd[0] = eventfd(0, EFD_CLOEXEC);  
  25.     if (base->th_notify_fd[0] >= 0) {  
  26.         evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  27.         notify = evthread_notify_base_eventfd;  
  28.         cb = evthread_notify_drain_eventfd;  
  29.     }  
  30. #endif  
  31. #if defined(_EVENT_HAVE_PIPE)  
  32.     //<0,说明之前的通知方式没有用上  
  33.     if (base->th_notify_fd[0] < 0) {  
  34.         //有些多路IO复用函数并不支持文件描述符。如果不支持,那么就不能使用这种  
  35.         //通知方式。有关这个的讨论.查看http://blog.csdn.net/luotuo44/article/details/38443569  
  36.         if ((base->evsel->features & EV_FEATURE_FDS)) {  
  37.             if (pipe(base->th_notify_fd) < 0) {  
  38.                 event_warn("%s: pipe", __func__);  
  39.             } else {  
  40.                 evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  41.                 evutil_make_socket_closeonexec(base->th_notify_fd[1]);  
  42.             }  
  43.         }  
  44.     }  
  45. #endif  
  46.   
  47. #ifdef WIN32  
  48. #define LOCAL_SOCKETPAIR_AF AF_INET  
  49. #else  
  50. #define LOCAL_SOCKETPAIR_AF AF_UNIX  
  51. #endif  
  52.   
  53.     if (base->th_notify_fd[0] < 0) {  
  54.         if (evutil_socketpair(LOCAL_SOCKETPAIR_AF, SOCK_STREAM, 0,  
  55.             base->th_notify_fd) == -1) {  
  56.             event_sock_warn(-1, "%s: socketpair", __func__);  
  57.             return (-1);  
  58.         } else {  
  59.             evutil_make_socket_closeonexec(base->th_notify_fd[0]);  
  60.             evutil_make_socket_closeonexec(base->th_notify_fd[1]);  
  61.         }  
  62.     }  
  63.   
  64.     //无论哪种通信机制,都要使得读端不能阻塞  
  65.     evutil_make_socket_nonblocking(base->th_notify_fd[0]);  
  66.   
  67.     //设置回调函数  
  68.     base->th_notify_fn = notify;  
  69.   
  70.     //同样为了让写端不阻塞。虽然,如果同时出现大量需要notify的操作,会塞满通信通道。  
  71.     //本次的notify会没有写入到通信通道中(已经变成非阻塞了)。但这无所谓,因为目的是  
  72.     //唤醒主线程,通信通道有数据就肯定能唤醒。  
  73.     if (base->th_notify_fd[1] > 0)  
  74.         evutil_make_socket_nonblocking(base->th_notify_fd[1]);  
  75.   
  76.     //该函数的作用等同于event_new。实际上event_new内部也是调用event_assign函数完成工作的  
  77.     //函数cb作为这个event的回调函数  
  78.     event_assign(&base->th_notify, base, base->th_notify_fd[0],  
  79.                  EV_READ|EV_PERSIST, cb, base);  
  80.   
  81.     //标明是内部使用的  
  82.     base->th_notify.ev_flags |= EVLIST_INTERNAL;  
  83.     event_priority_set(&base->th_notify, 0); //最高优先级  
  84.   
  85.     return event_add(&base->th_notify, NULL);//加入到event_base中。  
  86. }  

        上面代码展示了,Libevent会从eventfd、pipe和socketpair中选择一种通信方式。由于有三种通信通道可供选择,下文为了方便叙述,就假定它选定的是pipe。

        上面代码的工作过程和普通的Libevent例子程序差不多,首先创建一个文件描述符fd,然后用这个fd创建一个event,最后添加到event_base中。


唤醒流程:

        现在沿着这个内部的event的工作流程走一遍。


启动notify:

        首先往event写入一个字节,开启一切。由于这个event是内部的,用户是接触不到的。所以只能依靠Libevent提供的函数。当然这个函数也不会开放给用户,它只是供Libevent内部使用。

        现在来看Libevent内部的需要。在event_add_internal函数中需要通知主线程,在该函数的最后面会调用evthread_notify_base。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件  
  2. static int  
  3. evthread_notify_base(struct event_base *base)  
  4. {  
  5.     //确保已经加锁了  
  6.     EVENT_BASE_ASSERT_LOCKED(base);  
  7.     if (!base->th_notify_fn)  
  8.         return -1;  
  9.   
  10.     //写入一个字节,就能使event_base被唤醒。  
  11.     //如果处于未决状态,就必要写多一个字节  
  12.     if (base->is_notify_pending)  
  13.         return 0;  
  14.   
  15.     //通知处于未决状态,当event_base醒过来就变成已决的了。  
  16.     base->is_notify_pending = 1;  
  17.     return base->th_notify_fn(base);  
  18. }  

        在evthread_notify_base中,会调用th_notify_fn函数指针。这个指针是在前面的evthread_make_base_notifiable函数中被赋值的。这里以evthread_notify_base_default作为例子。这个evthread_notify_base_default完成实际的通知操作。


激活内部event:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c文件。  
  2. static int  
  3. evthread_notify_base_default(struct event_base *base)  
  4. {  
  5.     char buf[1];  
  6.     int r;  
  7.     buf[0] = (char) 0;  
  8.     //通知一下,用来唤醒。写一个字节足矣  
  9. #ifdef WIN32  
  10.     r = send(base->th_notify_fd[1], buf, 1, 0);  
  11. #else  
  12.     r = write(base->th_notify_fd[1], buf, 1);  
  13. #endif  
  14.     //即使errno 等于 EAGAIN也无所谓,因为这是由于通信通道已经塞满了  
  15.     //这已经能唤醒主线程了。没必要一定要再写入一个字节  
  16.     return (r < 0 && errno != EAGAIN) ? -1 : 0;  
  17. }  

        从上面两个函数看到,其实通知也是蛮简单的。只是往管道里面写入一个字节。当然这已经能使得event_base检测到管道可读,从而实现唤醒event_base。

        往管道写入一个字节,event_base就会被唤醒,然后调用这个管道对应event的回调函数。当然,在event_base醒来的时候,还能看到其他东西。这也是Libevent提供唤醒功能的原因。


        现在看一下这个唤醒event的回调函数,也是看默认的那个。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. //event.c  
  2. static void  
  3. evthread_notify_drain_default(evutil_socket_t fd, short what, void *arg)  
  4. {  
  5.     unsigned char buf[1024];  
  6.     struct event_base *base = arg;  
  7.   
  8.     //读完fd的所有数据,免得再次被唤醒  
  9. #ifdef WIN32  
  10.     while (recv(fd, (char*)buf, sizeof(buf), 0) > 0)  
  11.         ;  
  12. #else  
  13.     while (read(fd, (char*)buf, sizeof(buf)) > 0)  
  14.         ;  
  15. #endif  
  16.   
  17.     EVBASE_ACQUIRE_LOCK(base, th_base_lock);  
  18.     //修改之,使得其不再是未决的了。当然这也能让其他线程可以再次唤醒值。参看evthread_notify_base函数  
  19.     base->is_notify_pending = 0;  
  20.     EVBASE_RELEASE_LOCK(base, th_base_lock);  
  21. }  

        这个函数也比较简单,也就是读取完管道里的所有数据,免得被多路IO复用函数检测到管道可读,而再次被唤醒。

      

        上面的流程就完成了Libevent的通知唤醒主线程的功能,思路还是蛮清晰的。实现起来也是很简单。



注意事项:

        有一点要注意:要让Libevent支持这种可通知机制,就必须让Libevent使用多线程,即在代码的一开始调用evthread_use_pthreads()或者evthread_use_windows_threads()。虽然用户可以手动调用函数evthread_make_base_notifiable。但实际上是不能实现通知功能的。分析如下:

        Libevent代码中是通过调用函数evthread_notify_base来通知的。但这个函数都是在一个if语句中调用的。判断的条件为是否需要通知。If成立的条件中肯定会&&上一个EVBASE_NEED_NOTIFY(base)。比如在event_add_internal函数中的为:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))  
  2.         evthread_notify_base(base);  

        notify是根据函数中的判断而来,而EVBASE_NEED_NOTIFY这个宏定义的作用是判断当前的线程是否不等于主线程(即为event_base执行event_base_dispatch函数的线程)。它是一个条件宏。其中一个实现为:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define EVBASE_NEED_NOTIFY(base)             \  
  2.     (_evthread_id_fn != NULL &&          \  
  3.         (base)->running_loop &&           \  
  4.         (base)->th_owner_id != _evthread_id_fn())  
  5.   
  6. #define EVTHREAD_GET_ID() \  
  7.     (_evthread_id_fn ? _evthread_id_fn() : 1)  

        event_base结构体中th_owner_id变量指明当前为event_base执行event_base_dispatch函数的是哪个线程。在event_base_loop函数中用宏EVTHREAD_GET_ID()赋值。

        如果一开始没有调用evthread_use_pthreads或者evthread_use_windows_threads,那么全局变量evthread_id_fn就为NULL。也就不能获取线程的ID了。EVBASE_NEED_NOTIFY宏也只会返回0,使得不能调用evthread_notify_base函数。关于线程这部分的分析,可以参考《多线程、锁、条件变量(一)》和《多线程、锁、条件变量(二)》。


        下面的用一个例子验证。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<event.h>  
  2. #include<stdio.h>  
  3. #include<unistd.h>  
  4. #include<thread.h>  
  5.   
  6. #include<pthread.h> //Linux thread  
  7.   
  8. struct event_base *base = NULL;  
  9.   
  10.   
  11. void pipe_cb(int fd, short events, void *arg)  
  12. {  
  13.     printf("in the cmd_cb\n");  
  14. }  
  15.   
  16. void timeout_cb(int fd, short events, void *arg)  
  17. {  
  18.     printf("in the timeout_cb\n");  
  19. }  
  20.   
  21. void* thread_fn(void *arg)  
  22. {  
  23.     char ch;  
  24.     scanf("%c", &ch); //just for wait  
  25.   
  26.     struct event *ev = event_new(base, -1, EV_TIMEOUT | EV_PERSIST,  
  27.                                  timeout_cb, NULL);  
  28.   
  29.     struct timeval tv = {2, 0};  
  30.     event_add(ev, &tv);  
  31. }  
  32.   
  33. int main(int argc, char ** argv)  
  34. {  
  35.     if( argc >= 2 && argv[1][0] == 'y')  
  36.         evthread_use_pthreads();  
  37.   
  38.     base = event_base_new();  
  39.     evthread_make_base_notifiable(base);  
  40.   
  41.     int pipe_fd[2];  
  42.     pipe(pipe_fd);  
  43.   
  44.     struct event *ev = event_new(base, pipe_fd[0],  
  45.                                  EV_READ | EV_PERSIST, pipe_cb, NULL);  
  46.   
  47.     event_add(ev, NULL);  
  48.   
  49.     pthread_t thread;  
  50.     pthread_create(&thread, NULL, thread_fn, NULL);  
  51.   
  52.       
  53.     event_base_dispatch(base);  
  54.   
  55.     return 0;  
  56. }  

        如果次线程的event被add到event_base中,那么每2秒timeout_cb函数就会被调用一次。如果没有被add的话,就永远等待下去,没有任何输出。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值