Mô hình luồng của Binder được thiết kế để tạo điều kiện cho các lệnh gọi hàm cục bộ, ngay cả khi các lệnh gọi đó có thể là đến một quy trình từ xa. Cụ thể, mọi quy trình lưu trữ một nút đều phải có một nhóm gồm một hoặc nhiều luồng liên kết để xử lý các giao dịch đến các nút được lưu trữ trong quy trình đó.
Giao dịch đồng bộ và không đồng bộ
Binder hỗ trợ các giao dịch đồng bộ và không đồng bộ. Các phần sau đây giải thích cách thực thi từng loại giao dịch.
Giao dịch đồng bộ
Các giao dịch đồng bộ sẽ bị chặn cho đến khi được thực thi trên nút và người gọi nhận được phản hồi cho giao dịch đó. Hình sau đây cho thấy cách thực thi một giao dịch đồng bộ:
Hình 1. Giao dịch đồng bộ.
Để thực thi một giao dịch đồng bộ, Binder sẽ thực hiện những việc sau:
- Các luồng trong nhóm luồng mục tiêu (T2 và T3) gọi vào trình điều khiển hạt nhân để chờ công việc đến.
- Hạt nhân nhận được một giao dịch mới và đánh thức một luồng (T2) trong quy trình mục tiêu để xử lý giao dịch.
- Luồng gọi (T1) chặn và chờ phản hồi.
- Quy trình mục tiêu thực thi giao dịch và trả về phản hồi.
- Luồng trong quy trình mục tiêu (T2) gọi lại vào trình điều khiển hạt nhân để chờ công việc mới.
Giao dịch không đồng bộ
Các giao dịch không đồng bộ không chặn để hoàn tất; luồng gọi sẽ bỏ chặn ngay khi giao dịch được chuyển vào hạt nhân. Hình sau đây cho thấy cách thực thi một giao dịch không đồng bộ:
Hình 2. Giao dịch không đồng bộ.
- Các luồng trong nhóm luồng mục tiêu (T2 và T3) gọi vào trình điều khiển hạt nhân để chờ công việc đến.
- Hạt nhân nhận được một giao dịch mới và đánh thức một luồng (T2) trong quy trình mục tiêu để xử lý giao dịch.
- Luồng gọi (T1) tiếp tục thực thi.
- Quy trình mục tiêu thực thi giao dịch và trả về phản hồi.
- Luồng trong quy trình mục tiêu (T2) gọi lại vào trình điều khiển hạt nhân để chờ công việc mới.
Xác định hàm đồng bộ hoặc không đồng bộ
Các hàm được đánh dấu là oneway trong tệp AIDL là không đồng bộ. Ví dụ:
oneway void someCall();
Nếu một hàm không được đánh dấu là oneway, thì đó là hàm đồng bộ, ngay cả khi hàm đó trả về void.
Tuần tự hoá các giao dịch không đồng bộ
Binder tuần tự hoá các giao dịch không đồng bộ từ bất kỳ nút nào. Hình sau đây cho thấy cách Binder tuần tự hoá các giao dịch không đồng bộ:
Hình 3. Tuần tự hoá các giao dịch không đồng bộ.
- Các luồng trong nhóm luồng mục tiêu (B1 và B2) gọi vào trình điều khiển hạt nhân để chờ công việc đến.
- Hai giao dịch (T1 và T2) trên cùng một nút (N1) được gửi đến hạt nhân.
- Hạt nhân nhận được các giao dịch mới và vì chúng đến từ cùng một nút (N1), nên hạt nhân sẽ tuần tự hoá chúng.
- Một giao dịch khác trên một nút khác (N2) được gửi đến hạt nhân.
- Hạt nhân nhận được giao dịch thứ ba và đánh thức một luồng (B2) trong quy trình mục tiêu để xử lý giao dịch.
- Các quy trình mục tiêu thực thi từng giao dịch và trả về phản hồi.
Giao dịch lồng nhau
Các giao dịch đồng bộ có thể được lồng nhau; một luồng đang xử lý một giao dịch có thể phát hành một giao dịch mới. Giao dịch lồng nhau có thể là đến một quy trình khác hoặc đến cùng một quy trình mà bạn nhận được giao dịch hiện tại. Hành vi này mô phỏng các lệnh gọi hàm cục bộ. Ví dụ: giả sử bạn có một hàm có các hàm lồng nhau:
def outer_function(x):
def inner_function(y):
def inner_inner_function(z):
Nếu đây là các lệnh gọi cục bộ, chúng sẽ được thực thi trên cùng một luồng.
Cụ thể, nếu người gọi inner_function cũng là quy trình
lưu trữ nút triển khai inner_inner_function, thì lệnh gọi đến
inner_inner_function sẽ được thực thi trên cùng một luồng.
Hình sau đây cho thấy cách Binder xử lý các giao dịch lồng nhau:
Hình 4. Giao dịch lồng nhau.
- Luồng A1 yêu cầu chạy
foo(). - Trong yêu cầu này, luồng B1 chạy
bar()mà A chạy trên cùng một luồng A1.
Hình sau đây cho thấy quá trình thực thi luồng nếu nút triển khai bar() nằm trong một quy trình khác:
Hình 5. Giao dịch lồng nhau trong các quy trình khác nhau.
- Luồng A1 yêu cầu chạy
foo(). - Trong yêu cầu này, luồng B1 chạy
bar()chạy trong một luồng C1 khác.
Hình sau đây cho thấy cách luồng sử dụng lại cùng một quy trình ở bất kỳ đâu trong chuỗi giao dịch:
Hình 6. Giao dịch lồng nhau sử dụng lại một luồng.
- Quy trình A gọi vào quy trình B.
- Quy trình B gọi vào quy trình C.
- Sau đó, quy trình C gọi lại vào quy trình A và hạt nhân sử dụng lại luồng A1 trong quy trình A là một phần của chuỗi giao dịch.
Đối với các giao dịch không đồng bộ, việc lồng nhau không đóng vai trò gì; máy khách không chờ kết quả của một giao dịch không đồng bộ, vì vậy không có việc lồng nhau. Nếu trình xử lý của một giao dịch không đồng bộ gọi vào quy trình đã phát hành giao dịch không đồng bộ đó, thì giao dịch đó có thể được xử lý trên bất kỳ luồng nào rảnh trong quy trình đó.
Tránh tình trạng bế tắc
Hình sau đây cho thấy một tình trạng bế tắc phổ biến:
Hình 7. Tình trạng tắc nghẽn phổ biến.
- Quy trình A lấy mutex MA và thực hiện lệnh gọi liên kết (T1) đến quy trình B cũng cố gắng lấy mutex MB.
- Đồng thời, quy trình B lấy mutex MB và thực hiện lệnh gọi liên kết (T2) đến quy trình A cố gắng lấy mutex MA.
Nếu các giao dịch này chồng chéo, mỗi giao dịch có thể lấy một mutex trong quy trình của chúng trong khi chờ quy trình khác giải phóng mutex, dẫn đến tình trạng bế tắc.
Để tránh tình trạng bế tắc khi sử dụng Binder, đừng giữ bất kỳ khoá nào khi thực hiện lệnh gọi liên kết.
Quy tắc sắp xếp khoá và tình trạng bế tắc
Trong một môi trường thực thi duy nhất, tình trạng bế tắc thường được tránh bằng quy tắc sắp xếp khoá. Tuy nhiên, khi thực hiện các lệnh gọi giữa các quy trình và giữa các cơ sở mã, đặc biệt là khi mã được cập nhật, thì không thể duy trì và điều phối quy tắc sắp xếp.
Mutex đơn và tình trạng bế tắc
Với các giao dịch lồng nhau, quy trình B có thể gọi trực tiếp lại vào cùng một luồng trong quy trình A giữ một mutex. Do đó, do đệ quy không mong muốn, bạn vẫn có thể gặp tình trạng tắc nghẽn với một mutex đơn.
Lệnh gọi không đồng bộ và tình trạng bế tắc
Mặc dù các lệnh gọi liên kết không đồng bộ không chặn để hoàn tất, nhưng bạn cũng nên tránh giữ khoá cho các lệnh gọi không đồng bộ. Nếu giữ khoá, bạn có thể gặp phải vấn đề về khoá nếu lệnh gọi một chiều vô tình bị thay đổi thành lệnh gọi đồng bộ.
Luồng liên kết đơn và tình trạng bế tắc
Mô hình giao dịch của Binder cho phép nhập lại, vì vậy ngay cả khi một quy trình có một luồng liên kết đơn, bạn vẫn cần khoá. Ví dụ: giả sử bạn đang lặp lại một danh sách trong một quy trình A một luồng. Đối với mỗi mục trong danh sách, bạn sẽ thực hiện một giao dịch liên kết đi. Nếu quá trình triển khai hàm mà bạn đang gọi tạo một giao dịch liên kết mới đến một nút được lưu trữ trong quy trình A, thì giao dịch đó sẽ được xử lý trên cùng một luồng đang lặp lại danh sách. Nếu quá trình triển khai giao dịch đó sửa đổi cùng một danh sách, bạn có thể gặp phải vấn đề khi tiếp tục lặp lại danh sách sau này.
Định cấu hình kích thước nhóm luồng
Khi một dịch vụ có nhiều máy khách, việc thêm nhiều luồng vào nhóm luồng có thể giảm tình trạng tranh chấp và phục vụ nhiều lệnh gọi song song. Sau khi xử lý đúng cách tính đồng thời, bạn có thể thêm nhiều luồng hơn. Một vấn đề có thể xảy ra do việc thêm nhiều luồng mà một số luồng có thể không được sử dụng trong khối lượng công việc yên tĩnh.
Các luồng được tạo theo yêu cầu cho đến khi đạt đến mức tối đa đã định cấu hình. Sau khi một luồng liên kết được tạo, luồng đó sẽ hoạt động cho đến khi quy trình lưu trữ luồng đó kết thúc.
Thư viện libbinder có giá trị mặc định là 15 luồng. Sử dụng setThreadPoolMaxThreadCount để thay đổi giá trị này:
using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);