Binder 的執行緒模型旨在簡化本機函式呼叫,即使這些呼叫可能指向遠端程序也一樣。具體來說,任何代管節點的程序都必須有一或多個繫結器執行緒集區,才能處理該程序中代管節點的交易。
同步和非同步交易
Binder 支援同步和非同步交易。以下各節說明如何執行每種交易類型。
同步交易
同步交易會封鎖,直到在節點上執行完畢,且呼叫端收到該交易的回覆為止。下圖顯示如何執行同步交易:
圖 1. 同步交易。
如要執行同步交易,繫結器會執行下列操作:
- 目標執行緒集區 (T2 和 T3) 中的執行緒會呼叫核心驅動程式,等待傳入的工作。
- 核心會收到新的交易,並喚醒目標程序中的執行緒 (T2) 來處理交易。
- 呼叫執行緒 (T1) 會封鎖並等待回覆。
- 目標程序會執行交易並傳回回覆。
- 目標程序 (T2) 中的執行緒會回呼核心驅動程式,等待新工作。
非同步交易
非同步交易不會封鎖以等待完成;交易傳遞至核心後,呼叫執行緒就會解除封鎖。下圖顯示非同步交易的執行方式:
圖 2. 非同步交易。
- 目標執行緒集區 (T2 和 T3) 中的執行緒會呼叫核心驅動程式,等待傳入的工作。
- 核心會收到新的交易,並喚醒目標程序中的執行緒 (T2) 來處理交易。
- 呼叫執行緒 (T1) 會繼續執行。
- 目標程序會執行交易並傳回回覆。
- 目標程序 (T2) 中的執行緒會回呼核心驅動程式,等待新工作。
識別同步或非同步函式
AIDL 檔案中標示為 oneway
的函式為非同步函式。例如:
oneway void someCall();
如果函式未標示為 oneway
,即使函式傳回 void
,仍屬於同步函式。
非同步交易的序列化
Binder 會序列化來自任何單一節點的非同步交易。下圖顯示繫結器如何序列化非同步交易:
圖 3. 非同步交易的序列化。
- 目標執行緒集區 (B1 和 B2) 中的執行緒會呼叫核心驅動程式,等待傳入的工作。
- 同一節點 (N1) 上的兩筆交易 (T1 和 T2) 會傳送至核心。
- 核心會收到新的交易,由於這些交易來自同一個節點 (N1),因此會將其序列化。
- 另一個節點 (N2) 上的交易會傳送至核心。
- 核心會收到第三筆交易,並喚醒目標程序中的執行緒 (B2) 來處理交易。
- 目標程序會執行每筆交易,並傳回回覆。
巢狀交易
同步交易可以巢狀結構建立;處理交易的執行緒可以發出新交易。巢狀交易可以傳送至不同程序,也可以傳送至您接收目前交易的程序。這項行為會模擬本機函式呼叫。舉例來說,假設您有一個包含巢狀函式的函式:
def outer_function(x):
def inner_function(y):
def inner_inner_function(z):
如果是本機呼叫,則會在同一執行緒上執行。
具體來說,如果 inner_function
的呼叫端也是實作 inner_inner_function
的節點所代管的程序,則對 inner_inner_function
的呼叫會在同一執行緒上執行。
下圖顯示繫結器如何處理巢狀交易:
圖 4. 巢狀交易。
- 執行緒 A1 要求執行
foo()
。 - 在此要求中,執行緒 B1 會執行
bar()
,而 A 則會在相同執行緒 A1 上執行。
如果實作 bar()
的節點位於不同程序中,執行緒執行情形如下圖所示:
圖 5. 不同程序中的巢狀交易。
- 執行緒 A1 要求執行
foo()
。 - 在此要求中,執行
bar()
的是執行於另一個執行緒 C1 的執行緒 B1。
下圖顯示執行緒如何在交易鏈中的任何位置重複使用相同程序:
圖 6. 重複使用執行緒的巢狀交易。
- 程序 A 會呼叫程序 B。
- 程序 B 會呼叫程序 C。
- 接著,程序 C 會回呼程序 A,而核心會在程序 A 中重複使用屬於交易鏈的執行緒 A1。
對於非同步交易,巢狀結構沒有作用;用戶端不會等待非同步交易的結果,因此沒有巢狀結構。如果非同步交易的處理常式呼叫發出該非同步交易的程序,則該交易可在該程序中的任何可用執行緒上處理。
避免死結
下圖顯示常見的死結:
圖 7. 常見的死結。
- 程序 A 取得互斥鎖 MA,並向程序 B 發出繫結呼叫 (T1),程序 B 也嘗試取得互斥鎖 MB。
- 同時,程序 B 會取得互斥鎖 MB,並向程序 A 發出繫結呼叫 (T2),嘗試取得互斥鎖 MA。
如果這些交易重疊,每筆交易可能會在等待其他程序釋放互斥鎖時,在程序中取得互斥鎖,導致死結。
使用繫結器時,請勿在發出繫結器呼叫時持有任何鎖定,以免發生死結。
鎖定排序規則和死結
在單一執行環境中,通常會使用鎖定排序規則來避免鎖死。不過,在程序之間和程式碼庫之間進行呼叫時,特別是程式碼更新時,無法維護及協調排序規則。
單一互斥鎖和死結
使用巢狀交易時,程序 B 可以直接回呼程序 A 中保留互斥鎖的同一執行緒。因此,由於發生非預期的遞迴,單一互斥鎖仍可能造成死結。
同步呼叫和死結
雖然非同步繫結器呼叫不會封鎖完成作業,但您也應避免為非同步呼叫保留鎖定。如果持有鎖定,當單向呼叫意外變更為同步呼叫時,您可能會遇到鎖定問題。
單一繫結器執行緒和死結
Binder 的交易模型允許重入,因此即使程序只有單一繫結器執行緒,您仍需要鎖定。舉例來說,假設您在單一執行緒程序 A 中疊代清單,針對清單中的每個項目,您會建立外送繫結器交易。如果您呼叫的函式實作項目會對程序 A 中代管的節點建立新的繫結器交易,則該交易會在疊代清單的同一執行緒上處理。如果該交易的實作項目修改了相同清單,您稍後繼續疊代清單時可能會遇到問題。
設定執行緒集區大小
如果服務有多個用戶端,在執行緒集區中新增更多執行緒,可以減少爭用情形,並平行處理更多呼叫。正確處理並行問題後,即可新增更多執行緒。問題:新增更多執行緒可能會導致部分執行緒在閒置工作負載期間未使用。
系統會視需要產生執行緒,直到達到設定上限為止。產生繫結器執行緒後,該執行緒會持續運作,直到主機程序結束為止。
libbinder 程式庫預設為 15 個執行緒。使用
setThreadPoolMaxThreadCount
變更這個值:
using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);