Handler與Looper、MessageQueue共同實現了線程間消息傳遞。MessageQueue的底層實現是利用管道和epoll機制來實現的。
概括:當我們查看Looper.loop()方法時,會發現其中有一個無線循環。那么這其中的原因又是啥呢。當調用Looper.prepare()方法時,底層會創建一個管道,另外還會創建一個epoll實例去監聽管道的讀文件描述符。
當執行loop方法時,會調用epoll_wait去監聽epoll實例中所監聽的文件描述符有沒有對應的事件,如果沒有的話,該方法就會堵塞,所有for循環就沒有無限執行下去。當有其他線程向handler發送數據時,就會向管道寫數據,那么epoll_wait方法堵塞就會喚醒。
然后我們把管道中的數據都讀出來。下一次進行for循壞的時候又會調用epoll_wait方法,如果消息隊列沒有消息時,就又會堵塞了。
一:什么是管道
管道是一種把兩個進程之間的標準輸入和標準輸出連接起來的機制,從而提供一種讓多個進程間通信的方法。通俗一點說就是一個進程向管道寫入數據,另外一個進程可以從管道中讀取該數據。(ps:不只進程,線程當然也是可以的)
當進程創建管道時,每次都需要提供兩個文件描述符來操作管道。其中一個對管道進行寫操作,另一個對管道進行讀操作。
- 使用pipe函數創建一個管道
int pipefd[2];
pipe(pipefd);
當創建管道成功后,pipefd數組將會被賦值,其中pipefd[0]為管道的讀端文件描述符,pipedf[1]為管道的寫端文件描述符。
使用pipe函數創建是匿名管道,管道是沒有名字的,可以使用那倆個文件描述符對管道進行讀寫操作。
寫管道操作默認是堵塞的,也就是把數據全部寫入緩存后write函數才返回,如果緩存滿了就一直堵塞,直到緩存里的數據被讀出,數據被全部寫入。
二. epoll機制
epoll機制:可以同時監聽多個文件描述符的IO讀寫事件而設計的。
1. 創建一個epoll實例
int epfd = epoll_create(intsize);
參數 initsize 為epoll實例需要監聽的IO讀寫事件的數目大小。epfd 為epoll實例的文件描述符。
2. 使用epoll來監聽某個文件描述符上的事件
int epoll_ctl(int epfd, intop, int fd, struct epoll_event* event);
第一個參數是 epoll_create() 的返回值,
第二個參數 op 表示動作,用三個宏來表示:
EPOLL_CTL_ADD: 注冊新的fd到epfd中;
EPOLL_CTL_MOD: 修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL: 從epfd中刪除一個fd;
第三個參數是需要監聽的fd,
第四個參數是告訴內核需要監聽什么事件
舉例:利用epoll來監聽管道的讀文件描述符
int pipefd[2];
pipe(pipefd); //創建管道
int epfd = epoll_create(intsize); //創建epoll
struct epoll_event eventItem;
memset(& eventItem,0,sizeof(epoll_event)); // 給eventItem分配內存
eventItem.events = EPOLLIN; //EPOLLIN 表示對應的文件描述符上有可讀數據
eventItem.data.fd = pipefd[0];
result = epoll_ctl(epfd,EPOLL_CTL_ADD,pipefd[0],&eventItem);
分析: EventItem是我們定義的事件,事件的類型為EPOLLIN。表示事件為對應的文件描述符上有可讀數據,eventItem.data.fd 指定了特定文件描述符。調用epoll_ctl 去監聽管道讀文件描述符上是否有流到達
3. 使用epoll_wait 來監聽注冊在epoll實例中的文件描述符的IO讀寫事件
epoll實例可以注冊多個文件描述符,epoll_wait監聽epoll實例,這正是多路復用機制,一個epoll監聽了多個IO事件。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 參數events用來從內核得到事件的集合
- maxevents告之內核這個events有多大(數組成員的個數),這個maxevents的值不能大于創建epoll_create()時的size
- 參數timeout是超時時間(毫秒,0會立即返回,-1永久等待)
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMillis)
我們接著利用前面epoll_ctl 注冊管道的讀文件描述符。這里我們對epoll實例進行監聽等待。當管道的讀文件描述符沒有數據時,這里會被阻塞。當有其他線程或者進程向管道寫文件描述符寫入數據時,這時epoll_wait會將數據放入eventItems數組中,通過遍歷,我們可以拿到對應的事件。