【C++】线程安全加锁操作 Mutex::AutoLock 介绍

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 的使用,除了初始化外,最重要的是 lockunlock 函数的使用,它们的用法如下:

  • 要想独占互斥区域,必须先调用 Mutexlock 函数。这样,这个区域就被锁住了。如果这块区域之前已被别人锁住,lock 函数则会等待,直到可以进入这块区域为止。系统保证一次只有一个线程能 lock 成功。

  • 当你完成了操作,记得调用 Mutexunlock 以释放互斥区域。这样,其他线程的 lock 才可以成功返回。

另外,Mutex 还提供了一个 trylock 函数,该函数只是尝试去锁住该区域,使用者需要根据 trylock 的返回值来判断是否成功锁住了该区域。

注意:以上这些内容都和 Raw API 有关,不了解它的读者可自行学习相关知识。在 Android 系统中,多线程也是常见和重要的编程手段,务必请大家重视。Mutex 类确实比 Raw API 方便好用,不过还是稍显麻烦。

(2)AutoLock类是定义在Mutex内部的一个类,它其实是一帮“懒人”搞出来的,为什么这么说呢?先来看看使用Mutex有多麻烦:

  • 显示调用Mutexlock
  • 在某个时候记住要调用该Mutexunlock

以上这些操作都必须一一对应,否则会出现“死锁”!在有些代码中,如果判断分支特别多,你会发现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的用法很简单:

  1. 先定义一个Mutex,如 Mutex xlock
  2. 在使用xlock的地方,定义一个AutoLock,如 Mutex::Autolock autoLock(xlock)
  3. 由于C++对象的构造和析构函数都是自动被调用的,所以在AutoLock的生命周期内,xlocklockunlock也就自动被调用了,这样就省去了重复书写unlock的麻烦,而lockunlock的调用肯定是一一对应的,这样就绝对不会出错。

Autolock 构造时,主动调用内部成员变量 mLocklock() 方法,而在析构时正好相反,调用它的 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值