Skip to content

joinThreadPool

追踪 Binder 线程加入命令、请求循环、执行计数、超时退出和 BC_EXIT_LOOPER 清理路径。

基于android-17.0.0_r1
AndroidBinderjoinThreadPoolNative框架源码阅读

joinThreadPool ​

joinThreadPool() 不是“创建一个线程池”,而是让当前线程进入 Binder 消费循环。它先向驱动写入 BC_ENTER_LOOPER 或 BC_REGISTER_LOOPER,然后反复调用 getAndExecuteCommand();工作线程在 TIMED_OUT 时可以退出,主线程则继续等待;连接关闭或 fd 失效时,循环收束并发送 BC_EXIT_LOOPER,最后递减 mCurrentThreads。

本文面向已经读过 IPCThreadState线程绑定、ProcessState初始化 和 Binder线程管理 的读者。本文只讲 native 线程加入与退出循环,不重复讲 transact() 的发送细节,也不把 startThreadPool() 创建的线程与当前线程加入动作混为一谈。

1. 加入命令 ​

1.1 入口函数 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:IPCThreadState::joinThreadPool()

cpp
void IPCThreadState::joinThreadPool(bool isMain) {
    LOG_THREADPOOL("**** THREAD %p IS JOINING THE THREAD POOL", 
                   (void*)pthread_self());
    mProcess->checkExpectingThreadPoolStart();
    mProcess->mCurrentThreads++;
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER
                           : BC_REGISTER_LOOPER);
    mIsLooper = true;
    ...
}

isMain=true 表示当前线程发送 BC_ENTER_LOOPER;普通工作线程发送 BC_REGISTER_LOOPER。两者都增加进程级当前线程数并设置当前 IPCThreadState 的 mIsLooper。

1.2 配置警告 ​

checkExpectingThreadPoolStart() 只检查是否设置过非默认线程配置但尚未调用 startThreadPool(),并记录 warning。它不是加入动作的硬门槛;调用仍会继续进入循环。

1.3 线程来源 ​

源码文件:frameworks/native/libs/binder/ProcessState.cpp

相关函数:startThreadPool()、spawnPooledThread()

cpp
void ProcessState::startThreadPool() {
    std::unique_lock<std::mutex> lock(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

startThreadPool() 创建一个 PoolThread,其 threadLoop() 再调用 joinThreadPool(true);手动调用 joinThreadPool(false) 则由调用方线程直接加入。线程创建和线程加入是两个阶段。

2. 请求循环 ​

2.1 主循环 ​

cpp
status_t result;
do {
    processPendingDerefs();
    result = getAndExecuteCommand();

    if (result < NO_ERROR &&
            result != TIMED_OUT &&
            result != -ECONNREFUSED &&
            result != -EBADF) {
        LOG_ALWAYS_FATAL("getAndExecuteCommand returned unexpected error");
    }

    if (result == TIMED_OUT && !isMain) {
        break;
    }
} while (result != -ECONNREFUSED &&
         result != -EBADF);

每次迭代先处理待释放的 Binder 引用,再向驱动取并执行一个命令。只有列出的几类返回值被视为循环控制结果;其他负错误会 fatal,避免线程在未知状态下继续消费。

2.2 命令消费 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:getAndExecuteCommand()

cpp
result = talkWithDriver();
if (result >= NO_ERROR) {
    if (mIn.dataAvail() < sizeof(int32_t)) return result;
    int32_t cmd = mIn.readInt32();
    size_t executing =
            mProcess->mExecutingThreadsCount.fetch_add(1) + 1;
    result = executeCommand(cmd);
    size_t remaining =
            mProcess->mExecutingThreadsCount.fetch_sub(1) - 1;
    ...
}
return result;

mCurrentThreads 表示进入线程池的线程数,mExecutingThreadsCount 表示正在执行当前 Binder command 的线程数,不能用一个计数替代另一个。执行结束后,如果有线程等待可用,代码通过条件变量唤醒等待者。

2.3 资源释放 ​

processPendingDerefs() 在输入队列清空时处理延迟的 weak/strong deref。它放在每轮 command 前,避免 Binder 引用析构延迟到线程退出才发生;析构可能触发新的 outgoing transaction,所以实现使用外层循环直到 pending vectors 为空。

3. 退出条件 ​

3.1 工作线程超时 ​

工作线程 isMain=false 收到 TIMED_OUT 时跳出循环,以便线程池在需求下降时收缩。主线程即使超时也不会退出,因为 isMain=true 不满足 break 条件。

3.2 连接关闭 ​

-ECONNREFUSED 和 -EBADF 会结束主循环。前者通常表示驱动连接被拒绝,后者表示 fd 已关闭或失效;源码把这两种结果作为退出收束,而不是未知 fatal。

3.3 清理命令 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:joinThreadPool()

cpp
LOG_THREADPOOL("**** THREAD IS LEAVING THE THREAD POOL err=%d",
               result);
mOut.writeInt32(BC_EXIT_LOOPER);
mIsLooper = false;
if (status_t res = talkWithDriver(false); res != OK) {
    ALOGW("call to talkWithDriver in joinThreadPool returned error");
}
size_t oldCount = mProcess->mCurrentThreads.fetch_sub(1);
LOG_ALWAYS_FATAL_IF(oldCount == 0,
                    "Threadpool thread count underflowed");

退出时先通知驱动离开 looper,再清除线程本地标志,最后递减进程计数。计数下溢是内部不变量破坏,会 fatal;发送 BC_EXIT_LOOPER 失败只记录 warning。

4. 线程池边界 ​

4.1 最大线程数 ​

setThreadPoolMaxThreadCount() 设置的是驱动按需启动上限;joinThreadPool() 增加的是当前进程已进入线程池的数量。startThreadPool() 额外创建一个用户线程,再叠加驱动可能启动的线程和手动 join 的线程,最终总数不是单一字段的值。

4.2 starvation观测 ​

getAndExecuteCommand() 在执行线程数达到 mMaxThreads 时记录 starvation 起点;当执行数降回上限以下时计算持续时间,超过 100ms 记录线程池饥饿日志。这是观测和诊断,不是自动扩容或强制退出策略。

5. 测试边界 ​

5.1 线程可用数 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

ThreadPoolAvailableThreads 通过服务事务读取另一进程的最大线程数,随后并发占用线程并验证最终可用数;断言允许 kKernelThreads + 1 或 +2,源码明确承认线程计数存在竞态。它验证线程池总量近似关系,不证明每次 join 的绝对时序。

5.2 线程池启动 ​

ThreadPoolStarted 通过服务读取 ProcessState::isThreadPoolStarted() 并断言 true;测试进程 main 预先调用 ProcessState::startThreadPool()。它证明启动标志和 PoolThread 入口的组合,不单独测试 joinThreadPool() 的 timeout 退出。

5.3 可执行阅读 ​

bash
rg -n "joinThreadPool|BC_ENTER_LOOPER|BC_REGISTER_LOOPER|BC_EXIT_LOOPER|TIMED_OUT" \
  frameworks/native/libs/binder/IPCThreadState.cpp

rg -n "startThreadPool|spawnPooledThread|mCurrentThreads|mExecutingThreadsCount|starvation" \
  frameworks/native/libs/binder/ProcessState.cpp \
  frameworks/native/libs/binder/IPCThreadState.cpp

rg -n "ThreadPoolAvailableThreads|ThreadPoolStarted|joinThreadPool" \
  frameworks/native/libs/binder/tests/binderLibTest.cpp

排查 Binder 线程退出时,先区分 worker 的 TIMED_OUT、连接关闭的 ECONNREFUSED/EBADF 和未知负错误;若出现 count underflow,则应回到加入/退出配对和重复执行路径,而不是先调大线程上限。