新书上市《深入解析Android 5.0系统》
以下内容节选自本书
下面我们将通过一个实际的例子来进一步的了解Futex的用法。前面我们介绍Bionic中的线程管理时介绍了pthread的临界区函数。临界区的实现中最重要的是上锁和解锁函数,下面我们看看它们的实现:
1. 在Bionic的实现中,pthread_mutex_lock()调用了内部函数_normal_lock()来实现上锁功能,代码如下:
static__inline__ void _normal_lock(pthread_mutex_t* mutex, intshared)
{
constint unlocked = shared | MUTEX_STATE_BITS_UNLOCKED;
const int locked_uncontended = shared |MUTEX_STATE_BITS_LOCKED_UNCONTENDED;
if(__bionic_cmpxchg(unlocked, locked_uncontended,&mutex->value) != 0) {
constint locked_contended = shared | MUTEX_STATE_BITS_LOCKED_CONTENDED;
while(__bionic_swap(locked_contended, &mutex->value) !=unlocked)
__futex_wait_ex(&mutex->value,shared, locked_contended, 0);
}
ANDROID_MEMBAR_FULL();
}
_normal_lock()函数调用原子操作函数__bionic_cmpxchg()来检测mutex->value的值是否等于unlocked变量的值(unlocked代表未锁定的状态),如果相等则将mutex->value的值设置为locked_uncontended变量的值(表示锁定但是没有竞争的状态)。__bionic_cmpxchg()函数在mutex->value不等于unlocked的情况下会返回非0,这样就进入if语句内执行。
__bionic_swap()函数会将mutex->value的值设为locked_contended(表示有竞争),同时返回mutex->value原来的值。如果mutex->value原来的值不等于unlocked则调用函数__futex_wait_ex()挂起线程等待。这里使用while循环是因为可能有多个线程在等待,即使从挂起状态恢复后也可能还是抢不到锁,所以要重新进入等待状态。
2. pthread_mutex_unlock()则调用了内部函数_normal_unlock()来实现解锁功能。代码如下所示:
static__inline__ void _normal_unlock(pthread_mutex_t* mutex, intshared)
{
ANDROID_MEMBAR_FULL();
if (__bionic_atomic_dec(&mutex->value)!=
(shared|MUTEX_STATE_BITS_LOCKED_UNCONTENDED)){
mutex->value= shared;
__futex_wake_ex(&mutex->value,shared, 1);
}
}
解锁时首先使用__bionic_atomic_dec()函数对mutex->value执行减一操作,函数__bionic_atomic_dec()将返回mutex->value原来的值。如果mutex->value原来的值不是“锁定但是无竞争”的状态,也就意味着还有线程在等待,所以要调用__futex_wake_ex()来唤醒等待的线程。
从Mutex的实现代码中我们应该能体会到Futex的优点,如果多个线程访问临界区的时间是错开的,也就是没有真正的竞争发生,那么实际上是不会产生系统调用的。
当然如果多个线程在竞争临界区的锁,还是一样会有挂起和唤醒的系统调用。这种情况下 Futex 的效率和以前的实现就没有区别了。