Binder 线程池、oneway 与调用模型
Binder 线程池、oneway 与调用模型
上一章我们有了 Proxy 和 Stub。这一章回答:一次同步调用会阻塞谁?服务端代码由哪个线程 执行?oneway 又改变了什么?
同步 Binder 调用会阻塞发起调用的线程,直到服务端返回 reply;服务端 Stub 通常运行在 Binder 线程池,而不是主线程。接口看起来像本地方法,并不会消除这两个线程边界。
执行线程由 Binder 线程池决定
| 常见误解 | 正确理解 |
|---|---|
| 服务端代码运行在服务进程的主线程 | 默认运行在 Binder 线程池的某个线程,不是主线程 |
oneway 是“异步执行” | 它只表示客户端不等待 reply,服务端仍按正常事务处理,执行时机不受保证 |
| 调用 Binder 不会影响自己的线程 | 同步调用会让客户端调用线程阻塞到 reply 返回,主线程调用慢服务会 ANR |
oneway 只取消客户端等待 reply 的过程,不保证服务端立即执行,也不自动创建业务线程。 服务端仍需从 Binder 线程池取出事务;如果后续操作要求主线程语义,服务实现必须显式切换。
核心结论
- 同步调用:客户端调用线程进入
waitForResponse()阻塞,直到 reply 返回。 - 服务端:Binder 线程池中的线程执行
onTransact()与业务实现;服务端主线程不被占用。 - 线程池有上限,慢服务会占满线程池,其他调用排队等待。
oneway:客户端发送后不等待 reply,立即返回;异常不通过 reply 回传(DeadObjectException等仍需另行处理)。- 服务端代码要操作主线程或单线程状态时,必须主动切到 Handler/Executor,不能假设线程。
同步与 oneway 的调用模型
逐步解释:
- 客户端发起同步调用:调用线程阻塞,等待 reply;期间该线程不能做其他事。
- 驱动唤醒服务端线程:线程池中一个空闲线程取走事务并执行
onTransact()。 - 业务返回后写 reply:驱动唤醒客户端线程,
transact()返回。 - oneway 分支:客户端发送后不进入等待,事务在服务端照常执行,失败结果不回传。
线程池为什么有上限
每个 Binder 线程都是一条阻塞在 ioctl 上的系统线程,占用栈和调度资源。默认上限约 15 个, 是为了在“并发处理能力”和“资源开销”之间取平衡。如果服务端业务很慢,15 个线程全部占用 后,新事务只能排队,客户端表现为调用变慢或超时——这通常不是驱动问题,而是服务端吞吐不足。
源码证据
阅读目标:确认线程池的创建与上限,以及客户端阻塞的位置。
正文指针:ProcessState.cpp startThreadPool()/spawnPooledThread(); IPCThreadState.cpp transact()/waitForResponse()/executeCommand()。
// ProcessState.cpp(伪代码,行号以 r75 为准)
void ProcessState::startThreadPool() {
// 拉起第一个线程,之后按需要继续孵化
}
void ProcessState::spawnPooledThread(bool isMain) {
// 创建 Binder 线程并进入 talkWithDriver 循环
}// IPCThreadState.cpp:客户端同步等待 reply(伪代码,行号以 r75 为准)
status_t IPCThreadState::waitForResponse(Parcel* reply, status_t* acquireResult) {
// 循环 talkWithDriver,直到收到 BR_REPLY 或错误
}这段代码证明: 客户端在 waitForResponse() 里阻塞;服务端线程由 ProcessState 按 需孵化并进入驱动读取循环。两者的线程都来自各自进程,驱动不创建也不执行线程。
验证与排障
环境:任意开发设备即可;部分命令需要 root。
adb shell ps -T -p <服务端PID> | grep -c binder
adb shell cat /proc/<服务端PID>/task/*/comm 2>/dev/null | grep binder服务端进程里名字带 binder 的线程就是 Binder 线程池线程;数量接近上限说明吞吐吃紧。 客户端主线程长时间卡在同步 Binder 调用时,ANR trace 里会出现等待 reply 的栈,这是 “服务端慢”而不是“客户端死循环”的信号。
常见误区
- “服务端代码在主线程执行”:默认在 Binder 线程池线程,需要自己切换线程。
- “
oneway一定更快执行”:它只省去等待,服务端仍然排队,不保证执行顺序与时效。 - “线程池不够就多开”:盲目调大上限只是把压力后移,先解决服务端慢的根本原因。
- “主线程调 Binder 没问题”:慢服务会把主线程卡死,导致 ANR。
延伸问题
joinThreadPool()与startThreadPool()的关系是什么?- 驱动在什么条件下会创建“新线程”投递事务,什么条件下排队?
- 服务端如何安全地把 Binder 回调切回主线程(Handler/Looper)?
oneway调用在发送方线程上的顺序性如何保证?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
ProcessState.cpp | startThreadPool() / spawnPooledThread() | 线程池启动与孵化 |
IPCThreadState.cpp | transact() / waitForResponse() | 客户端发送与阻塞等待 |
IPCThreadState.cpp | executeCommand() / BR_TRANSACTION | 服务端事务执行 |
BinderInternal.java | ThreadPool 相关 | Java 侧线程池接入 |
公共路径:Native 侧 frameworks/native/libs/binder/,Java 侧 frameworks/base/core/java/com/android/internal/os/。行号以 r75 检索为准。
远端进程退出后的引用处理,见 死亡通知。