Android 中 Zygote 进程启动以后,负责 fork 应用进程,在 fork 出应用进程以后,会为应用进程启动 Binder 机制,具体在onZygoteInit()
中:
// frameworks/base/cmds/app_process/app_main.cpp
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
}
sp<ProcessState> ProcessState::self() {
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
return gProcess;
}
gProcess = new ProcessState("/dev/binder");
return gProcess;
}
在 onZygoteInit()
中,主要调用了 ProcessState::self()
,这个函数主要返回了一个 gProcess
单例,并且这里使用了 Mutex
互斥锁。
互斥类 — Mutex
Mutex
是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源。在《Windows核心编程》一书中,对于这种互斥访问有一个很形象的比喻:想象你在飞机上如厕,这时卫生间的信息牌上显示“有人”,你必须等里面的人出来后才可进去。这就是互斥的含义。
下面来看Mutex
的实现方式,它们都很简单。
(1)Mutex介绍
其代码如下所示:
// [-->`Thread.h::Mutex`的声明和实现]
inline Mutex::Mutex(int type, const char* name) {
if (type == SHARED) {
// 如果type是SHARED,则表示这个Mutex支持跨进程的线程同步。
// 以后我们在Audio系统和Surface系统中会经常见到这种用法。
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mMutex, &attr);
pthread_mutexattr_destroy(&attr);
} else {
pthread_mutex_init(&mMutex, NULL);
}
}
inline Mutex::~Mutex() {
pthread_mutex_destroy(&mMutex);
}
inline status_t Mutex::lock() {
return -pthread_mutex_lock(&mMutex);
}
inline void Mutex::unlock() {
pthread_mutex_unlock(&mMutex);
}
inline status_t Mutex::tryLock() {
return -pthread_mutex_trylock(&mMutex);
}
关于 Mutex
的使用,除了初始化外,最重要的是 lock
和 unlock
函数的使用,它们的用法如下:
-
要想独占互斥区域,必须先调用
Mutex
的lock
函数。这样,这个区域就被锁住了。如果这块区域之前已被别人锁住,lock
函数则会等待,直到可以进入这块区域为止。系统保证一次只有一个线程能lock
成功。 -
当你完成了操作,记得调用
Mutex
的unlock
以释放互斥区域。这样,其他线程的lock
才可以成功返回。
另外,Mutex
还提供了一个 trylock
函数,该函数只是尝试去锁住该区域,使用者需要根据 trylock
的返回值来判断是否成功锁住了该区域。
注意:以上这些内容都和 Raw API 有关,不了解它的读者可自行学习相关知识。在 Android 系统中,多线程也是常见和重要的编程手段,务必请大家重视。Mutex
类确实比 Raw API 方便好用,不过还是稍显麻烦。
(2)AutoLock
类是定义在Mutex
内部的一个类,它其实是一帮“懒人”搞出来的,为什么这么说呢?先来看看使用Mutex
有多麻烦:
- 显示调用
Mutex
的lock
。 - 在某个时候记住要调用该
Mutex
的unlock
。
以上这些操作都必须一一对应,否则会出现“死锁”!在有些代码中,如果判断分支特别多,你会发现unlock
这句代码被写得比比皆是,如果稍有不慎,在某处就会忘了写它。有什么好办法能解决这个问题吗?终于有人想出来一个好办法,就是充分利用了C++的构造和析构函数,只需看一看AutoLock
的定义就会明白。代码如下所示:
// [-->Thread.h Mutex::Autolock声明和实现]
class Autolock {
public:
// 调用lock的构造函数
inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
// 调用unlock的析构函数
inline ~Autolock() { mLock.unlock(); }
private:
Mutex& mLock;
};
AutoLock的用法很简单:
- 先定义一个
Mutex
,如Mutex xlock
。 - 在使用
xlock
的地方,定义一个AutoLock
,如Mutex::Autolock autoLock(xlock)
。 - 由于C++对象的构造和析构函数都是自动被调用的,所以在
AutoLock
的生命周期内,xlock
的lock
和unlock
也就自动被调用了,这样就省去了重复书写unlock
的麻烦,而lock
和unlock
的调用肯定是一一对应的,这样就绝对不会出错。
当 Autolock
构造时,主动调用内部成员变量 mLock
的 lock()
方法,而在析构时正好相反,调用它的 unlock()
方法释放锁。这样的话,假如一个 Autolock
对象是局部变量,则在生命周期结束时就自动的把资源锁解了。举个 AudioTrack
中的例子,如下所示:
/*frameworks/av/media/libmedia/AudioTrack.cpp*/
uint32_t audio_track_cblk_t::framesAvailable()
{
Mutex::Autolock _l(lock);
return framesAvailable_l();
}
变量 _l
就是一个 Autolock
对象,它在构造时会主动调用 audio_track_cblk_t
中的 lock
锁,而当 framesAvailable()
结束时, _l
的生命周期也随之完结,于是 lock
所对应的锁也会被打开。这是一个实现上的小技巧,在某些情况下可以有效防止开发人员没有配套使用 lock
/unlock
。