背景
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