android Binder設計與實現七

8 數據包接收隊列與(線程)等待隊列管理

通常數據傳輸的接收端有兩個隊列:數據包接收隊列和(線程)等待隊列,用以緩解供需矛盾。當超市裡的進貨(數據包)太多,貨物會堆積在倉庫裡;購物的人(線程)太多,會排隊等待在收銀臺,道理是一樣的。在驅動中,每個進程有一個全局的接收隊列,也叫to-do隊列,存放不是發往特定線程的數據包;相應地有一個全局等待隊列,所有等待從全局接收隊列裡收數據的線程在該隊列裡排隊。每個線程有自己私有的to-do隊列,存放發送給該線程的數據包;相應的每個線程都有各自私有等待隊列,專門用於本線程等待接收自己to-do隊列裡的數據。雖然名叫隊列,其實線程私有等待隊列中最多隻有一個線程,即它自己。

由於發送時沒有特別標記,驅動怎麼判斷哪些數據包該送入全局to-do隊列,哪些數據包該送入特定線程的to-do隊列呢?這裡有兩條規則。規則 1:Client發給Server的請求數據包都提交到Server進程的全局to-do隊列。不過有個特例,就是上節談到的Binder對工作線程啟動 的優化。經過優化,來自T1的請求不是提交給P2的全局to-do隊列,而是送入瞭T2的私有to-do隊列。規則2:對同步請求的返回數據包(由 BC_REPLY發送的包)都發送到發起請求的線程的私有to-do隊列中。如上面的例子,如果進程P1的線程T1發給進程P2的線程T2的是同步請求, 那麼T2返回的數據包將送進T1的私有to-do隊列而不會提交到P1的全局to-do隊列。

數據包進入接收隊列的潛規則也就決定瞭線程進入等待隊列的潛規則,即一個線程隻要不接收返回數據包則應該在全局等待隊列中等待新任務,否則就應該在其私有等待隊列中等待Server的返回數據。還是上面的例子,T1在向T2發送同步請求後就必須等待在它私有等待隊列中,而不是在P1的全局等待隊列中排隊,否則將得不到T2的返回的數據包。

這些潛規則是驅動對Binder通信雙方施加的限制條件,體現在應用程序上就是同步請求交互過程中的線程一致性:1) Client端,等待返回包的線程必須是發送請求的線程,而不能由一個線程發送請求包,另一個線程等待接收包,否則將收不到返回包;2) Server端,發送對應返回數據包的線程必須是收到請求數據包的線程,否則返回的數據包將無法送交發送請求的線程。這是因為返回數據包的目的 Binder不是用戶指定的,而是驅動記錄在收到請求數據包的線程裡,如果發送返回包的線程不是收到請求包的線程驅動將無從知曉返回包將送往何處。

接下來探討一下Binder驅動是如何遞交同步交互和異步交互的。我們知道,同步交互和異步交互的區別是同步交互的發送(client)端在發出請 求數據包後須要等待接收(Server)端的返回數據包,而異步交互的發送端發出請求數據包後交互即結束。對於這兩種交互的請求數據包,驅動可以不管三七二十一,統統丟到接收端的to-do隊列中一個個處理。但驅動並沒有這樣做,而是對異步交互做瞭限流,令其為同步交互讓路,具體做法是:對於某個 Binder實體,隻要有一個異步交互沒有處理完畢,例如正在被某個線程處理或還在任意一條to-do隊列中排隊,那麼接下來發給該實體的異步交互包將不再投遞到to-do隊列中,而是阻塞在驅動為該實體開辟的異步交互接收隊列(Binder節點的async_todo域)中,但這期間同步交互依舊不受限 制直接進入to-do隊列獲得處理。一直到該異步交互處理完畢下一個異步交互方可以脫離異步交互隊列進入to-do隊列中。之所以要這麼做是因為同步交互 的請求端需要等待返回包,必須迅速處理完畢以免影響請求端的響應速度,而異步交互屬於‘發射後不管’,稍微延時一點不會阻塞其它線程。所以用專門隊列將過 多的異步交互暫存起來,以免突發大量異步交互擠占Server端的處理能力或耗盡線程池裡的線程,進而阻塞同步交互。

9 總結

Binder使用Client-Server通信方式,安全性好,簡單高效,再加上其面向對象的設計思想,獨特的接收緩存管理和線程池管理方式,成為Android進程間通信的中流砥柱。

 

摘自 LuoXianXiong,您的夥伴

發佈留言