跟着文档重学binder之线程处理部分

处理线程

Binder 的线程处理模型旨在方便本地函数调用,即使这些调用可能是对远程进程的调用。具体而言,托管节点的任何进程都必须有一个或多个 binder 线程池来处理向该进程中托管的节点的事务。

同步和异步事务

Binder 支持同步和异步事务。以下各部分介绍了每种事务类型的执行方式。

同步事务

同步事务会一直处于阻塞状态,直到在节点上执行完毕,并且调用方收到该事务的回复。下图展示了同步事务的执行方式:
在这里插入图片描述

图 1. 同步事务。

如需执行同步事务,binder 会执行以下操作:

目标线程池(T2 和 T3)中的线程会调用内核驱动程序来等待传入的工作。

内核接收到新事务,并唤醒目标进程中的线程 (T2) 来处理该事务。

调用线程 (T1) 会阻塞并等待回复。

目标进程执行事务并返回回复。

目标进程 (T2) 中的线程会回调到内核驱动程序中,以等待新工作。

异步事务

异步事务不会阻塞以等待完成;一旦事务传递到内核,调用线程就会解除阻塞。下图展示了如何执行异步事务:

异步事务。

图 2. 异步事务。

目标线程池(T2 和 T3)中的线程会调用内核驱动程序来等待传入的工作。

内核接收到新事务,并唤醒目标进程中的线程 (T2) 来处理该事务。

调用线程 (T1) 继续执行。

目标进程执行事务并返回回复。

目标进程 (T2) 中的线程会回调到内核驱动程序中,以等待新工作。

识别同步函数或异步函数

在 AIDL 文件中标记为 oneway 的函数是异步的。例如:

oneway void someCall();

如果函数未标记为 oneway,则即使该函数返回 void,它也是同步函数。
注意: 包含被调用方的进程可能会在未处理函数调用且未提供任何失败信息的情况下终止或冻结。

异步事务的序列化

Binder 会序列化来自任何单个节点的异步事务。下图展示了 binder 如何序列化异步事务:

异步事务的序列化

图 3. 异步事务的序列化。

1、目标线程池(B1 和 B2)中的线程会调用内核驱动程序来等待传入的工作。

2、同一节点 (N1) 上的两个事务(T1 和 T2)被发送到内核。

3、内核收到新事务,由于它们来自同一节点 (N1),因此会对其进行序列化。

4、另一个节点 (N2) 上的另一事务被发送到内核。

5、内核接收第三个事务,并唤醒目标进程中的线程 (B2) 来处理该事务。

6、目标进程执行每项事务并返回回复。

注意: 同一节点上的异步事务不一定由线程池中的同一线程处理。而是可以在不同的线程上处理,但绝不能同时处理。

嵌套事务

同步事务可以嵌套;处理事务的线程可以发出新事务。嵌套事务可以发送到其他进程,也可以发送到您接收当前事务的同一进程。此行为会模拟本地函数调用。例如,假设您有一个包含嵌套函数的函数:

def outer_function(x):
def inner_function(y):
def inner_inner_function(z):

如果这些是本地调用,则会在同一线程上执行。 具体而言,如果 inner_function 的调用方恰好也是实现 inner_inner_function 的节点的宿主进程,则对 inner_inner_function 的调用会在同一线程上执行。

下图展示了 binder 如何处理嵌套事务:

嵌套事务。

图 4. 嵌套事务。

线程 A1 请求运行 foo()。
在此请求中,线程 B1 运行 bar(),而 A 在同一线程 A1 上运行。

下图显示了实现 bar() 的节点位于不同进程中的线程执行情况:

不同进程中的嵌套事务。

图 5. 不同进程中的嵌套事务。

线程 A1 请求运行 foo()。
在此请求中,线程 B1 运行 bar(),而 bar() 在另一个线程 C1 中运行。

下图显示了线程如何在事务链中的任何位置重复使用同一进程:

重用线程的嵌套事务。

图 6. 重用线程的嵌套事务。

进程 A 调用进程 B。
进程 B 调用进程 C。
然后,进程 C 回调进程 A,内核会重用进程 A 中属于事务链的线程 A1。

对于异步事务,嵌套不起作用;客户端不会等待异步事务的结果,因此不存在嵌套。如果异步事务的处理程序调用了发出该异步事务的进程,则可以在该进程中的任何空闲线程上处理该事务。

避免死锁

下图展示了一个常见的死锁:

常见死锁

图 7. 常见死锁。

进程 A 获取互斥锁 MA 并向进程 B 发出 binder 调用 (T1),进程 B 也尝试获取互斥锁 MB。

与此同时,进程 B 获取互斥锁 MB 并向进程 A 发出 binder 调用 (T2),进程 A 尝试获取互斥锁 MA。

如果这些事务重叠,每个事务都可能会在其进程中获取互斥锁,同时等待另一个进程释放互斥锁,从而导致死锁。

为避免在使用 binder 时出现死锁,请勿在进行 binder 调用时持有任何锁。

锁定顺序规则和死锁

在单个执行环境中,通常可以通过锁定顺序规则来避免死锁。不过,在进程之间和代码库之间进行调用时,尤其是在代码更新时,无法维护和协调排序规则。

单个互斥锁和死锁

借助嵌套事务,进程 B 可以直接调用回进程 A 中持有互斥锁的同一线程。因此,由于意外的递归,仍有可能因单个互斥锁而发生死锁。

同步调用和死锁

虽然异步 binder 调用不会阻塞以等待完成,但您也应避免为异步调用持有锁。如果您持有锁,则可能会遇到锁定问题,因为单向调用会被意外更改为同步调用。

单个 binder 线程和死锁

Binder 的事务模型允许重入,因此即使进程只有一个 binder 线程,您仍然需要锁定。例如,假设您正在单线程进程 A 中迭代处理一个列表。对于列表中的每个项目,您都会进行一次传出 binder 事务。如果您调用的函数的实现向进程 A 中托管的节点发起了新的 binder 事务,则该事务会在迭代列表的同一线程上处理。如果该事务的实现修改了同一列表,那么您稍后继续迭代该列表时可能会遇到问题。

配置线程池大小

当服务有多个客户端时,向线程池添加更多线程可以减少争用并并行处理更多调用。正确处理并发问题后,您可以添加更多线程。添加更多线程可能会导致一些线程在工作负载较少时未被使用。

注意: 与普通的非 binder 函数调用一样,您需要考虑由可重入调用引起的并发问题。

系统会根据需要生成线程,直到达到配置的最大值。生成绑定器线程后,该线程会一直保持活跃状态,直到托管它的进程结束。

libbinder 库的默认线程数为 15。使用 setThreadPoolMaxThreadCount 更改此值:

using ::android::ProcessState;
ProcessState::self()->setThreadPoolMaxThreadCount(size_t maxThreads);

更多framework实战开发干货,请关注下面“千里马学框架”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值