binder线程安全即读取线程池部分剖析

背景

hi,粉丝朋友们:
大家好!近期有学员在学习binder过程中向我提出了2个疑问:
1、binder是否线程安全的,即同一个binder的服务端方法是不是同一个时间点,只有一个执行者?
2、binder的读取线程是怎么启动的?又怎么来的多个读取线程

上面问题是不是感觉还是比较经典的两个问题,这个要回答上面两个问题,就需要搞清楚binder通讯过程的读取线程池部分,下面针对以上两个问题来挨个分析一下

线程安全问题

这个问题其实判断是否线程安全还是比较简单的,只需要判断多个客户端请求时候,服务端方法执行是串行执行还是说并行执行。
在这里插入图片描述

即只需要简单看看多个client 请求时候服务端onTransact的调用情况,如果说多个client请求onTransact方法还是按顺序一个个请求执行,那么就代表是线程安全的,如果onTransact方法出现多个同时执行,那么就代表非线程安全,这里需要针对onTransact的方法做一点特殊处理,即要在onTransact中故意加一个耗时延时,让onTransact执行时间久一点,那样方便验证,不然可能存在onTransact执行太快无法确认的情况

这里使用跨进程课程demo进行改造:

class SampleService: public BBinder {
public:
  SampleService() {
    ALOGE("Server ------------------------------ %d",__LINE__);
    mydescriptor = String16(SAMPLE_SERIVCE_DES);
  }

  virtual ~SampleService() {
  }

  virtual const String16& getInterfaceDescriptor() const {
    return mydescriptor;
  }

protected:
//这里的callFunction就是服务端执行的业务方法
  void callFunction(int val) {
    printf("Server callFunction begin------------------------------ %d\n",__LINE__);
    sleep(2);//加入一个2s延时来模拟服务端耗时,防止onTransact执行太快没办法确定是否顺序执行
    printf( "Service:callFunction end  %s(), %d, val = %d \n",__FUNCTION__,__LINE__,val);
  }

  virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) {
    printf( "Service onTransact,line = %d, code = %d\n",__LINE__, code);
    switch (code) {
    case SRV_CODE:
      //读取Client传过来的IBinder对象
      callback = data.readStrongBinder();

      if(callback != NULL)
       {
         Parcel _data, _reply;
	 _data.writeInt32(1);
	 _data.writeInt32(2);
	 _data.writeInt32(3);

	 //2.String8类型
	 _data.writeString8(String8("who..."));
	 _data.writeString8(String8("are..."));
	 _data.writeString8(String8("you..."));
	 //回调客户端
         int ret = callback->transact(CB_CODE, _data, &_reply, 0);
         ALOGD("callback->transact ret %d\n",ret);
       }
      //调用server端的
      callFunction(6666);
      break;
    default:
      return BBinder::onTransact(code, data, reply, flags);
    }
    return 0;
  }

private:
  String16 mydescriptor;
  sp<IBinder> callback;
};
int main() {
  sp<IServiceManager> sm = defaultServiceManager();
  SampleService* samServ = new SampleService();
  status_t ret = sm->addService(String16(SAMPLE_SERIVCE_DES), samServ);
  //注意这里没有开启读取线程池
  //ProcessState::self()->startThreadPool();
  IPCThreadState::self()->joinThreadPool( true);
  return 0;
}

改造的唯一一行代码callFunction中加入了 sleep(2)延时代表耗时,还有一个要注意点就是ProcessState::self()->startThreadPool()没有被调用,即没有开启读取线程池,是靠IPCThreadState::self()->joinThreadPool( true)主线程进行join读取执行,客户端不需要任何变化
那么来看看执行情况:
这里采用两个客户端端先后间隔很近情况下执行请求
在这里插入图片描述

明显看到两次请求几乎同时发出(间隔时间很短),但是服务端onTransact执行时却是顺序执行,所以这种情况是线程安全的。

这里我们再把ProcessState::self()->startThreadPool()代码放开

int main() {
  sp<IServiceManager> sm = defaultServiceManager();
  SampleService* samServ = new SampleService();
  status_t ret = sm->addService(String16(SAMPLE_SERIVCE_DES), samServ);
  //注意这里没有开启读取线程池
  ProcessState::self()->startThreadPool();
  IPCThreadState::self()->joinThreadPool( true);
  return 0;
}

执行结果如下
在这里插入图片描述
明显看到这个里每个client请求来了立即执行,没有等待上一个onTransact执行完成,就开始下一个onTransact,所以这里明显就是线程不安全的情况。

那么究竟开放ProcessState::self()->startThreadPool()和不开放有啥区别呢?
这里其实就需要分析源码了,不过可以先通过命令看看对应进程的线程情况

不开放ProcessState::self()->startThreadPool()情况下发现进程实在只有一个线程存在,而且还是主线程

130|NX563J:/ # ps -A | grep server_bi
root          2847  2734 10832476  2388 binder_thread_read  0 S server_binder_Callback
NX563J:/ # ps -T -p 2847
USER           PID   TID  PPID     VSZ    RSS WCHAN            ADDR S CMD            
root          2847  2847  2734 10832476  2388 binder_th+          0 S server_binder_C
NX563J:/ # ps -T -p 2847                                                                               
USER           PID   TID  PPID     VSZ    RSS WCHAN            ADDR S CMD            
root          2847  2847  2734 10832476  2388 hrtimer_n+          0 S server_binder_C

开放ProcessState::self()->startThreadPool()情况下发现进程实有多个binder线程存在

NX563J:/ # ps -A | grep server_bi
root          9048  8991 10868400  2392 binder_thread_read  0 S server_binder_Callback
NX563J:/ # ps -A | grep server_bi^C                                                                    
130|NX563J:/ # ps -T -p 9048  
USER           PID   TID  PPID     VSZ    RSS WCHAN            ADDR S CMD            
root          9048  9048  8991 10868400  2392 binder_th+          0 S server_binder_C
root          9048  9050  8991 10868400  2392 binder_th+          0 S binder:9048_1
root          9048  9188  8991 10868400  2392 binder_th+          0 S binder:9048_2
root          9048  9189  8991 10868400  2392 binder_th+          0 S binder:9048_3
NX563J:/ # 

总结:
binder调用服务端是否属于线程安全情况:
1、极少情况下,只有一个单线程接受执行binder调用时候,是属于线程安全
2、只要存在多个binder线程执行,就不是线程安全的,大部分场景都是有多个binder线程情况

下面来看看导致进程可以有多个binder线程关键方法ProcessState::self()->startThreadPool()

读取线程池部分源码

ProcessState::self()->startThreadPool()启动读取线程部分

分析一下ProcessState::self()->startThreadPool()源码

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

主要调用是spawnPooledThread方法

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();//创建线程名字
        sp<Thread> t = sp<PoolThread>::make(isMain);//创建PoolThread线程
        t->run(name.string());//这里启动线程运行
    }
}

上面就是简单的创建了PoolThread线程然后启动运行,看看PoolThread运行时候到底是干了啥


class PoolThread : public Thread
{
public:
    explicit PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }

protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);//仅仅调用了
        return false;
    }

    const bool mIsMain;
};

可以看到这里PoolThread主要就是调用了IPCThreadState::self()->joinThreadPool(mIsMain)方法
这个方法如下:

void IPCThreadState::joinThreadPool(bool isMain)
{
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    mIsLooper = true;
    status_t result;
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();

        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            LOG_ALWAYS_FATAL("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
                  mProcess->mDriverFD, result);
        }

        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    mOut.writeInt32(BC_EXIT_LOOPER);
    mIsLooper = false;
    talkWithDriver(false);
}

其实就是不断的读取内核的发送过来的binder信息和执行相关的方法。

那么上面我们清楚了整个读取线程启动,但是上面只有在初始化时候调用了一次ProcessState::startThreadPool方法,也就是只启动一个binder线程,明显上面shell命令看的时候是有多个啊,哪里还有渠道来扩张binder读取线程么?

扩张binder读取线程部分

这里在IPCThreadState::executeCommand执行读取binder驱动相关数据时候会有个如下操作BR_SPAWN_LOOPER情况,这个情况就会调用spawnPooledThread,spawnPooledThread上面已经看过来启动更多的binder线程,只不过这类传递的isMain是false,即不是binder主线程
在这里插入图片描述
那么这里的BR_SPAWN_LOOPER又是哪里来的呢?这里明显看到是BR的消息所以是从binder驱动来的,具体如下:
在这里插入图片描述

可以看到主要就是判断一下
1、进程是否有waiting_threads线程
2、是否超过当前的最大线程,即这个就体现出来客户端设置binder最大线程作用了

如果没有等待工作线程和也没有操作最大线程数既可以发送BR_SPAWN_LOOPER重新开启一个新的binder线程

更多framework干货获取相关可以 私聊+v(androidframework007)
点击这里 https://mp.weixin.qq.com/s/Qv8zjgQ0CkalKmvi8tMGaw
视频:https://www.bilibili.com/video/BV1ah411d7Y3
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/222217.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RetsCloud AppLink适用的场景有哪些?

Applink是什么产品&#xff1f; AppLink是一款由RestCloud公司推出的超级应用连接器。无需开发&#xff0c;零代码&#xff0c;即可快速打通应用系统之间的数据。通过流程搭建&#xff0c;可以智能、高效地完成自动化任务&#xff0c;在大大提高工作效率的同时&#xff0c;也降…

状态设计模式

package com.jmj.pattern.state.after;public abstract class LiftState {protected Context context;public void setContext(Context context) {this.context context;}//电梯开启操作public abstract void open();//电梯关闭操作public abstract void close();//电梯运行操…

SystemWeaver—电子电气系统协同研发平台

背景概述 当前电子电气系统在汽车领域应用广泛&#xff0c;其设计整合了多门工程学科&#xff0c;也因系统的复杂性、关联性日益提升&#xff0c;需要其提供面向软件、硬件、网络、电气等多领域交织而导致的复杂系统解决方案。并且随着功能安全、AUTOSAR、SOA、以太网通讯等新要…

python中各式各样的字典操作

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python中&#xff0c;字典&#xff08;Dictionary&#xff09;是一种强大而灵活的数据结构&#xff0c;它允许你存储和检索键值对。本文将深入探讨Python中各式各样的字典操作&#xff0c;包括基本操作、高级操…

解析与预防:Java中的内存泄漏问题

目录 引言 1. 内存泄漏的定义 2. 内存泄漏的常见原因 2.1 引用保留 2.2 长生命周期的对象持有短生命周期对象的引用 3. 检测内存泄漏的手段 3.1 内存分析工具 3.2 日志和监控 4. 预防内存泄漏的方法 4.1 及时释放资源 4.2 使用弱引用 4.3 避免静态引用 5. 结语 引…

CSS进阶知识点速览2

1 前情回顾 关于选择器进阶、背景色、元素显示模式和css特性的前半部分集中在下面的笔记中&#xff1a; css进阶知识点速览 2 CSS特性 2.1 优先级 特性&#xff1a;不同选择器具有不同的优先级&#xff0c;优先级高的选择器样式会覆盖优先级低选择器样式 优先级公式&#x…

【面试HOT200】二叉树的构建二叉搜索树篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot200】进行的&#xff0c;每个知识点的修正和深入主要参…

vr工业制造流程3D模拟仿真可视化展示

工业仿真3D数字化展示系统具有多方面的独特之处&#xff0c;主要体现在以下几个方面&#xff1a; 1、真实感和交互性&#xff1a;该系统可以将实际的工业设备、产品、场景等进行数字化建模&#xff0c;通过三维图形技术将其呈现在计算机屏幕上&#xff0c;使用户可以在虚拟环境…

ospf选路

问题描述 R6通过主备份路径访问LSP&#xff08;R1&#xff09;&#xff0c;主为R2&#xff0c; 备为R3 解决方案 路由器1看作LSP&#xff0c;配置loopback 0 ,地址为1.1.1.1 供测试使用&#xff1b;路由器 236, LSW4和LSW5&#xff0c; 运行ospf处于相同区域&#xff0c;建立…

【荣誉】科东软件荣获广州市软件行业协会双料大奖!

软件产业在数字经济中扮演着基础支撑的角色&#xff0c;对于优化产业结构、提高自主可控、赋能整体经济高质量发展具有关键作用。 近日&#xff0c;广州市软件行业第七届会员大会第三次会议成功召开&#xff01;此次会议旨在回顾过去一年的行业发展&#xff0c;展望未来的趋势和…

HarmonyOS应用开发——页面

我们将对于多页面以及更多有趣的功能展开叙述&#xff0c;这次我们对于 HarmonyOS 的很多有趣常用组件并引出一些其他概念以及解决方案、页面跳转传值、生命周期、启动模式&#xff08;UiAbility&#xff09;&#xff0c;样式的书写、状态管理以及动画等方面进行探讨 页面之间…

C++基础 -45- 类的静态数据成员

类的静态成员不包含在对象空间内 举例验证 定义普通变量和静态的变量 输出可知静态成员并没有占用类空间 静态数据成员的赋值&#xff08;必须类外赋值&#xff09; int base:: b 100;静态数据成员的访问&#xff08;不需要先定义对象&#xff09; int main() {cout <…

最新关于openai.APIConnectionError: Connection error.的解决方法

其实是和以前一样的处理方式&#xff0c;&#xff08;挂魔法&#xff09;修改代理&#xff0c;但是openai的源码改了&#xff0c;好多博客的方法不能用了。现在给一个新的修改方式&#xff0c;自己用的&#xff0c;发现可以。 1.找到pip下载的openai的Lib&#xff0c;找到_base…

揭秘AI魔法绘画:Stable Diffusion引领无限创意新纪元

文章目录 1. 无限的创意空间2. 高效的创作过程3. 个性化的艺术表达4. 跨界合作的可能性5. 艺术教育的革新6. 艺术市场的拓展 《AI魔法绘画&#xff1a;用Stable Diffusion挑战无限可能》编辑推荐内容简介作者简介精彩书评目录前言/序言本书读者对象学习建议获取方式 随着科技的…

SpringCloud微服务 【实用篇】| http客户端Feign

目录 一&#xff1a;http客户端Feign 1. Feign替代RestTemplate 2. 自定义配置 3. Feign性能优化 4. 最佳实践 前言 前些天突然发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c;感兴趣的同学可以…

开启三层交换机DHCP服务

二层交换机上不需要配置任何东西&#xff0c;只需要在pc机上开启dhcp服务&#xff0c;配置好LSW1后就可以自动获取到IP地址。 sys Enter system view, return user view with CtrlZ. [Huawei]sys sw1 [sw1]dhcp enable Info: The operation may take a few seconds. Please wai…

MES管理系统在生产计划排程中的应用与价值

随着制造业市场竞争的日益激烈和客户需求的多样化&#xff0c;传统的生产计划排程方式已经无法满足企业的需求。为了提升生产计划的效率和准确性&#xff0c;越来越多的企业开始引入MES管理系统这一先进的工具。那么&#xff0c;MES管理系统到底是什么&#xff0c;又是如何解决…

基于c++版本的数据结构改-python栈和队列思维总结

##栈部分-&#xff08;叠猫猫&#xff09; ##抽象数据类型栈的定义&#xff1a;是一种遵循先入后出的逻辑的线性数据结构。 换种方式去理解这种数据结构如果我们在一摞盘子中取到下面的盘子&#xff0c;我们首先要把最上面的盘子依次拿走&#xff0c;才可以继续拿下面的盘子&…

Nodejs+vue+ElementUi自动排课系统

使用自动排课系统分为管理员和学生、教师三个角色的权限子模块。 管理员所能使用的功能主要有&#xff1a;首页、个人中心、学生管理、教师管理、班级信息管理、专业信息管理、教室信息管理、课程信息管理、排课信息管理、系统管理等。 学生可以实现首页、个人中心、排课信息管…

uni-app 微信小程序之新增 添加小程序的交互

文章目录 1. 实现效果2. 提示组件 1. 实现效果 2. 提示组件 在 components 中新增 struggler-uniapp-add-tip 提示添加小程序 组件默认展示&#xff0c;通过点击将 SHOW_TIP 存储本地进行隐藏 <template><view><view class"uni-add-tips-box" v-if&…