標籤:核心 epoll android 源碼 thread
1 開頭
上一講講到Looper,大家對Looper有了大概的了結(好幾個月過去了…)
大家都知道一個Handler對應有一個MessageQueue,
在哪個線程上new Handler(如果不指定looper對象),那麼這個handler就預設對應於這個線程上的prepare過的Looper
如Handler.java代碼所示,mLooper由Looper.myLooper()指定,
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
而Looper.myLooper()來自此線程裡儲存的looper對象(在looper.prepare時存入)
public static Looper myLooper() { return sThreadLocal.get(); }
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
#include <iostream>#include <vector>#include <queue>#include <pthread.h>#include <unistd.h>#include <sys/epoll.h>#include <assert.h>#include <fcntl.h>#define NUM_THREAD 4#define NUM_LENGTH 200#define MAX_EVENTS 20#define USES_EPOLL#ifdef USES_EPOLL/****(1).建立一個epoll描述符,調用epoll_create()來完成,epoll_create()有一個整型的參數size,用來告訴核心,要建立一個有size個描述符的事件列表(集合)int epoll_create(int size)(2).給描述符設定所關注的事件,並把它添加到核心的事件列表中去,這裡需要調用epoll_ctl()來完成。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)這裡op參數有三種,分別代表三種操作:a. EPOLL_CTL_ADD, 把要關注的描述符和對其關注的事件的結構,添加到核心的事件列表中去b. EPOLL_CTL_DEL,把先前添加的描述符和對其關注的事件的結構,從核心的事件列表中去除c. EPOLL_CTL_MOD,修改先前添加到核心的事件列表中的描述符的關注的事件(3). 等待核心通知事件發生,得到發生事件的描述符的結構列表,該過程由epoll_wait()完成。得到事件列表後,就可以進行事件處理了。int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)– EPOLLIN,讀事件– EPOLLOUT,寫事件– EPOLLPRI,帶外資料,與select的例外狀況事件集合對應– EPOLLRDHUP,TCP串連對端至少寫寫半關閉– EPOLLERR,錯誤事件– EPOLLET,設定事件為邊沿觸發– EPOLLONESHOT,只觸發一次,事件自動被刪除*/int g_epollfd;int g_wakeFds[2];#endifvoid awake(){ ssize_t nWrite; do { nWrite = write(g_wakeFds[1], "W", 1); } while (nWrite == -1);}void awoken() { char buffer[16]; ssize_t nRead; do { nRead = read(g_wakeFds[0], buffer, sizeof(buffer)); } while ((nRead == -1 ) || nRead == sizeof(buffer));}using namespace std;void* threadRead(void* userdata){ queue<int>* q = (queue<int>*)userdata; struct epoll_event events[MAX_EVENTS]; while( true ) { int fds = epoll_wait(g_epollfd, events, MAX_EVENTS, 1000); if(fds < 0){ printf("epoll_wait error, exit\n"); break; } for(int i = 0; i < fds; i++){ if( events[i].events & EPOLLIN ) // read event { printf("%s,%d/%d\n", "EPOLLIN",i,fds); while( !q->empty() ) { q->pop(); printf("removed! \n" ); } } } awoken(); } return userdata;}void* threadRun(void* userdata){queue<int>* q = (queue<int>*)userdata;while( true ) {#ifdef USES_EPOLL q->push( 1 ); printf("%ld:%s\n",(long)pthread_self() ,"added!"); awake();#else#endif usleep(1000*500); } printf("exit thread:%ld\n",(long)pthread_self() );return userdata;}int main(int argc, char const *argv[]){/**pipe(建立管道):1) 標頭檔 #include<unistd.h>2) 定義函數: int pipe(int filedes[2]);3) 函數說明: pipe()會建立管道,並將檔案描述詞由參數filedes數組返回。 filedes[0]為管道裡的讀取端 filedes[1]則為管道的寫入端。*/ int result = pipe(g_wakeFds); assert( result!=0 ); result = fcntl(g_wakeFds[0], F_SETFL, O_NONBLOCK); assert(result!=0); result = fcntl(g_wakeFds[1], F_SETFL, O_NONBLOCK); assert(result!=0); g_epollfd = epoll_create( MAX_EVENTS ); assert( g_epollfd > 0 ); struct epoll_event epv = {0, {0}}; //epv.data.ptr = userdata; epv.data.fd = g_wakeFds[0]; epv.events = EPOLLIN; if(epoll_ctl(g_epollfd, EPOLL_CTL_ADD, g_wakeFds[0], &epv) < 0) printf("Event Add failed[fd=%d], evnets[%d]\n", epv.data.fd, epv.events); else printf("Event Add OK[fd=%d], op=%d, evnets[%0X]\n", epv.data.fd, EPOLL_CTL_ADD, epv.events); queue<int> q; vector<pthread_t> v;for (int i = 0; i < NUM_THREAD; ++i){pthread_t tid;pthread_create(&tid,NULL,threadRun,&q);v.push_back(tid);} pthread_t tid; pthread_create(&tid,NULL,threadRead,&q); v.push_back(tid); for(vector<pthread_t>::const_iterator it = v.begin(); it < v.end(); ++it) pthread_join(*it,NULL);return 0;}
大致思路是這樣的:
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 原始碼