【从源码看Android】02MessageQueue的epoll原型

来源:互联网 发布:淘宝xboxone美版二手 编辑:程序博客网 时间:2024/05/06 21:30

1 开头

上一讲讲到Looper,大家对Looper有了大概的了结(好几个月过去了…)

大家都知道一个Handler对应有一个MessageQueue,

在哪个线程上new Handler(如果不指定looper对象),那么这个handler就默认对应于这个线程上的prepare过的Looper

如下图Handler.java代码所示,mLooper由Looper.myLooper()指定,

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public Handler(Callback callback, boolean async) {  
  2.         if (FIND_POTENTIAL_LEAKS) {  
  3.             final Class<? extends Handler> klass = getClass();  
  4.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  5.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
  6.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  7.                     klass.getCanonicalName());  
  8.             }  
  9.         }  
  10.   
  11.         mLooper = Looper.myLooper();  
  12.         if (mLooper == null) {  
  13.             throw new RuntimeException(  
  14.                 "Can't create handler inside thread that has not called Looper.prepare()");  
  15.         }  
  16.         mQueue = mLooper.mQueue;  
  17.         mCallback = callback;  
  18.         mAsynchronous = async;  
  19.     }  

而Looper.myLooper()来自此线程里保存的looper对象(在looper.prepare时存入)

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static Looper myLooper() {  
  2.         return sThreadLocal.get();  
  3.     }  

so,一个handler,对应了一套MessageQueue、Thread、Looper

这些都是 【从源码看Android】01从Looper说起 讲过的东西,那么下面来些硬货



2 一个问题引入

从一个问题引入,如果在子线程12上创建了一个handler,

现在在主线程上调用handler.sendEmptyMessage,

handler如何在主线程上处理这个msg,

然后从子线程12让handler的handleMessage函数处理呢?


那么这个时候就要引入一个跨线程的事件模型--epoll,

这一讲先把cpp epoll模型讲清楚,

下一讲再讲android里如何利用这个模型的



3 epoll模型

epolldemo.cpp

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <vector>  
  3. #include <queue>  
  4. #include <pthread.h>  
  5. #include <unistd.h>  
  6. #include <sys/epoll.h>  
  7. #include <assert.h>  
  8. #include <fcntl.h>  
  9.   
  10. #define NUM_THREAD 4  
  11. #define NUM_LENGTH 200  
  12. #define MAX_EVENTS 20  
  13.   
  14. #define USES_EPOLL  
  15.   
  16. #ifdef USES_EPOLL  
  17. /**** 
  18.  
  19. (1).创建一个epoll描述符,调用epoll_create()来完成,epoll_create()有一个整型的参数size,用来告诉内核,要创建一个有size个描述符的事件列表(集合) 
  20. int epoll_create(int size) 
  21.  
  22. (2).给描述符设置所关注的事件,并把它添加到内核的事件列表中去,这里需要调用epoll_ctl()来完成。 
  23. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 
  24. 这里op参数有三种,分别代表三种操作: 
  25. a. EPOLL_CTL_ADD, 把要关注的描述符和对其关注的事件的结构,添加到内核的事件列表中去 
  26. b. EPOLL_CTL_DEL,把先前添加的描述符和对其关注的事件的结构,从内核的事件列表中去除 
  27. c. EPOLL_CTL_MOD,修改先前添加到内核的事件列表中的描述符的关注的事件 
  28.  
  29. (3). 等待内核通知事件发生,得到发生事件的描述符的结构列表,该过程由epoll_wait()完成。得到事件列表后,就可以进行事件处理了。 
  30. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 
  31.  
  32.  
  33. – EPOLLIN,读事件 
  34. – EPOLLOUT,写事件 
  35. – EPOLLPRI,带外数据,与select的异常事件集合对应 
  36. – EPOLLRDHUP,TCP连接对端至少写写半关闭 
  37. – EPOLLERR,错误事件 
  38. – EPOLLET,设置事件为边沿触发 
  39. – EPOLLONESHOT,只触发一次,事件自动被删除 
  40.  
  41.  
  42. */  
  43. int g_epollfd;  
  44. int g_wakeFds[2];  
  45. #endif  
  46.   
  47.   
  48. void awake()  
  49. {  
  50.     ssize_t nWrite;  
  51.     do   
  52.     {  
  53.         nWrite = write(g_wakeFds[1], "W", 1);  
  54.     }   
  55.     while (nWrite == -1);  
  56. }  
  57.   
  58. void awoken()   
  59. {  
  60.   
  61.     char buffer[16];  
  62.     ssize_t nRead;  
  63.     do {  
  64.         nRead = read(g_wakeFds[0], buffer, sizeof(buffer));  
  65.     } while ((nRead == -1 ) || nRead == sizeof(buffer));  
  66. }  
  67.   
  68. using namespace std;  
  69. void* threadRead(void* userdata)  
  70. {  
  71.     queue<int>* q = (queue<int>*)userdata;  
  72.   
  73.     struct epoll_event events[MAX_EVENTS];  
  74.     whiletrue )  
  75.     {  
  76.         int fds = epoll_wait(g_epollfd, events, MAX_EVENTS, 1000);  
  77.         if(fds < 0){  
  78.             printf("epoll_wait error, exit\n");  
  79.             break;  
  80.         }  
  81.   
  82.         for(int i = 0; i < fds; i++){  
  83.             if( events[i].events & EPOLLIN ) // read event  
  84.             {  
  85.                 printf("%s,%d/%d\n""EPOLLIN",i,fds);  
  86.                 while( !q->empty() )  
  87.                 {  
  88.                     q->pop();  
  89.                     printf("removed! \n" );  
  90.                 }  
  91.             }  
  92.         }  
  93.         awoken();  
  94.     }  
  95.     return userdata;  
  96. }  
  97.   
  98. void* threadRun(void* userdata)  
  99. {  
  100.     queue<int>* q = (queue<int>*)userdata;  
  101.     whiletrue )  
  102.     {  
  103.   
  104. #ifdef USES_EPOLL  
  105.         q->push( 1 );  
  106.         printf("%ld:%s\n",(long)pthread_self() ,"added!");  
  107.         awake();  
  108.   
  109. #else  
  110. #endif  
  111.         usleep(1000*500);  
  112.     }  
  113.     printf("exit thread:%ld\n",(long)pthread_self() );  
  114.     return userdata;  
  115. }  
  116.   
  117. int main(int argc, char const *argv[])  
  118. {  
  119. /** 
  120.     pipe(建立管道): 
  121. 1) 头文件 #include<unistd.h> 
  122. 2) 定义函数: int pipe(int filedes[2]); 
  123. 3) 函数说明: pipe()会建立管道,并将文件描述词由参数filedes数组返回。 
  124.               filedes[0]为管道里的读取端 
  125.               filedes[1]则为管道的写入端。 
  126. */  
  127.     int result = pipe(g_wakeFds);  
  128.     assert( result!=0 );  
  129.   
  130.     result = fcntl(g_wakeFds[0], F_SETFL, O_NONBLOCK);  
  131.     assert(result!=0);  
  132.   
  133.     result = fcntl(g_wakeFds[1], F_SETFL, O_NONBLOCK);  
  134.     assert(result!=0);  
  135.   
  136.     g_epollfd = epoll_create( MAX_EVENTS );  
  137.     assert( g_epollfd > 0 );  
  138.   
  139.     struct epoll_event epv = {0, {0}};  
  140.     //epv.data.ptr = userdata;  
  141.     epv.data.fd = g_wakeFds[0];  
  142.     epv.events = EPOLLIN;  
  143.   
  144.     if(epoll_ctl(g_epollfd, EPOLL_CTL_ADD, g_wakeFds[0], &epv) < 0)  
  145.         printf("Event Add failed[fd=%d], evnets[%d]\n", epv.data.fd, epv.events);  
  146.     else  
  147.         printf("Event Add OK[fd=%d], op=%d, evnets[%0X]\n", epv.data.fd, EPOLL_CTL_ADD, epv.events);  
  148.   
  149.     queue<int> q;  
  150.     vector<pthread_t> v;  
  151.     for (int i = 0; i < NUM_THREAD; ++i)  
  152.     {  
  153.         pthread_t tid;  
  154.         pthread_create(&tid,NULL,threadRun,&q);  
  155.         v.push_back(tid);  
  156.     }  
  157.   
  158.     pthread_t tid;  
  159.     pthread_create(&tid,NULL,threadRead,&q);  
  160.     v.push_back(tid);  
  161.         
  162.     for(vector<pthread_t>::const_iterator it = v.begin(); it < v.end(); ++it)  
  163.         pthread_join(*it,NULL);  
  164.   
  165.     return 0;  
  166. }  


大致思路是这样的:

a.127行开始建立管道g_wakeFds,g_wakeFds[0]是读取端口,g_wakeFds[1]是写入端口

b.136行创建全局的g_epollfd,即epoll文件描述符,参数为这个文件描述符所支持的最大事件数

c.144行epoll_ctl创建一个事件关联,即将g_epollfd与g_wakeFds[0]进行关联,如果g_wakeFds[0]发生变化,就会触发事件,并且事件为139创建的epoll_event实例

d.151-156行创建多个线程作为生产者,生产int放入queue中,放入完后调用awake()函数,向g_wakeFds[1]写入一字节,触发事件

f.158-160行创建一个消费者来消费生产的int

g.其中76行int fds = epoll_wait(g_epollfd, events, MAX_EVENTS, 1000);来等待生产者生产的int,当g_wakeFds[1]有数据写入时,g_wakeFds[0]就会触发刚刚注册的事件,获取到注册的事件后对事件进行处理(消费int),随后调用awoken()清空g_wakeFds[0],进入下一轮epoll_wait


注意:生产enqueue和消费dequeue是需要同步锁的,这里省略了这个过程,android在java中对Message实现的同步锁


4 运行结果



5 源码下载

http://pan.baidu.com/s/1i3BTWpv


6 总结

当一个线程的消息队列没有消息需要处理时,它就会在这个管道的读端文件描述符上进行睡眠等待,直到其他线程通过这个管道的写端文件描述符来唤醒它。这样就节省了线程上对于cpu资源的消耗。


7 reference

《Android系统源码情景分析》- 罗升阳

Android NDK 源代码

Android SDK 源代码


0 0
原创粉丝点击