然后我们进入源码分析。
bionic将条件变量封装成了一个类,也就成为了Condition
这也是Bionic的设计理念之一,就是让事情变得真正简单化。
* Condition variables are paired up with mutexes. Lock the mutex,
* call wait(), then either re-wait() if things aren't quite what you want,
* or unlock the mutex and continue. All threads calling wait() must
* use the same mutex for a given Condition.
头文件位于//common/frameworks/base/include/utils/Condition.h
class Condition {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
Condition();
Condition(int type);
~Condition();
// Wait on the condition variable. Lock the mutex before calling.
status_t wait(Mutex& mutex);
// same with relative timeout
status_t waitRelative(Mutex& mutex, nsecs_t reltime);
// Signal the condition variable, allowing one thread to continue.
void signal();
// Signal the condition variable, allowing all threads to continue.
void broadcast();
private:
#if defined(HAVE_PTHREADS)
pthread_cond_t mCond;
#else
void* mState;
#endif
};
跟Mutex一样,Condition也可以分成两种类型:private和shared
/* Process shared or private flag. */
enum
{
PTHREAD_PROCESS_PRIVATE,
#define PTHREAD_PROCESS_PRIVATE PTHREAD_PROCESS_PRIVATE
PTHREAD_PROCESS_SHARED
#define PTHREAD_PROCESS_SHARED PTHREAD_PROCESS_SHARED
};
基本的宏
/* We use one bit in condition variable values as the 'shared' flag
* The rest is a counter.
*/
#define COND_SHARED_MASK 0x0001
#define COND_COUNTER_INCREMENT 0x0002
#define COND_COUNTER_MASK (~COND_SHARED_MASK)
#define COND_IS_SHARED(c) (((c)->value & COND_SHARED_MASK) != 0)
相当于pthread_cond_t 长度为4个字节,1bit表示其flag 是private还是shared
其他的都用语表示counter。
1. 基本结构
bionic 针对pthread用到的变量主要是:
typedef struct
{
int volatile value;
} pthread_cond_t;
typedef struct
{
uint32_t flags;
void * stack_base;
size_t stack_size;
size_t guard_size;
int32_t sched_policy;
int32_t sched_priority;
} pthread_attr_t;
见common/bionic/libc/include/pthread.h
2. 变量初始化
先看一句linux高级编程对条件变量的描述:条件变量是线程可用的另一种同步机制,条件变量给多个线程提供了一个会和的场所,条件变量和互斥量一起使用的,允许线程以无竞争的方式等待特定的条件发生。
条件变量本身是由互斥量保护的,线程在改变条件状态前必须首先锁住互斥量。
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr)
{
if (cond == NULL)
return EINVAL;
cond->value = 0;
if (attr != NULL && *attr == PTHREAD_PROCESS_SHARED)
cond->value |= COND_SHARED_MASK;
return 0;
}
与mutex一样,pthread中,变量和attr相辅相承的,根据attr的信息,修改pthread_cond_t的value。
3. wait
inline status_t Condition::wait(Mutex& mutex) {
return -pthread_cond_wait(&mCond, &mutex.mMutex);
}
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
return pthread_cond_timedwait(cond, mutex, NULL);
}
int __pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t * mutex,
const struct timespec *abstime,
clockid_t clock)
{
struct timespec ts;
struct timespec * tsp;
if (abstime != NULL) {
if (__timespec_to_absolute(&ts, abstime, clock) < 0)
return ETIMEDOUT;
tsp = &ts;
} else {
tsp = NULL;
}
return __pthread_cond_timedwait_relative(cond, mutex, tsp);
}
int __pthread_cond_timedwait_relative(pthread_cond_t *cond,
pthread_mutex_t * mutex,
const struct timespec *reltime)
{
int status;
int oldvalue = cond->value;
pthread_mutex_unlock(mutex);//解锁
status = __futex_wait_ex(&cond->value, COND_IS_SHARED(cond), oldvalue, reltime);
pthread_mutex_lock(mutex); //锁住
if (status == (-ETIMEDOUT)) return ETIMEDOUT;
return 0;
}
int __futex_wait_ex(volatile void *ftx, int pshared, int val, const struct timespec *timeout)
{
return __futex_syscall4(ftx, pshared ? FUTEX_WAIT : FUTEX_WAIT_PRIVATE, val, timeout);
}
这就是调用流程,最后调用了__futex_wait_ex()
我们很奇怪的是为什么通常进入了wait之前还要用pthread_mutex_lock来锁住mutex,难道不会死锁?
但从linux高级编程上的描述来说,pthread_cond_wait是干了这么一件事:函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。
而当pthread_cond_wait返回时,互斥量再次被锁住。
为什么要绕这么一圈:
pthead_cond_wait之前调用pthread_mutex_lock,其真正目的是为了把mutex的控制权还给调用pthread_cond_wait的线程;
再往底层抠点:以x86为例common/bionic/libc/arch-x86/bionic/futex_x86.S
/*int __futex_syscall4(volatile void *ftx, int op, int val, const struct timespec *timeout) */
.text
.globl __futex_syscall4
.type __futex_syscall4, @function
.align 4
__futex_syscall4:
pushl %ebx
pushl %esi
movl 12(%esp), %ebx /* ftx */
movl 16(%esp), %ecx /* op */
movl 20(%esp), %edx /* val */
movl 24(%esp), %esi /* timeout */
movl $__NR_futex, %eax
int $0x80
popl %esi
popl %ebx
ret
common/bionic/libc/include/sys/linux-syscalls.h中定义了系统调用
#define __NR_futex (__NR_SYSCALL_BASE + 240)
其实这个就是调用了futex(ftx, op, val, timeout)
futex 的等待操作可以指定条件。将常量 FUTEX_WAIT 作为参数传递给 futex 作为第二个参数 op 的值,这个时候第三个参数 op 的值将作为一个比较对象。
当 *(int *)futex == val 的时候,调用体将被阻塞直到 timeout 时间到达,或者被其它执行体唤醒。
如果 *(int *)futex != val,则锁定过程会被直接跳过。
至于futex需要继续研究。
4. signal 唤醒等待mCond的线程
inline void Condition::signal() {
pthread_cond_signal(&mCond);
}
int pthread_cond_signal(pthread_cond_t *cond)
{
return __pthread_cond_pulse(cond, 1);
}
/* This function is used by pthread_cond_broadcast and
* pthread_cond_signal to atomically decrement the counter
* then wake-up 'counter' threads.
*/
static int
__pthread_cond_pulse(pthread_cond_t *cond, int counter)
{
long flags;
if (__unlikely(cond == NULL))
return EINVAL;
flags = (cond->value & ~COND_COUNTER_MASK);
for (;;) {
long oldval = cond->value;
long newval = ((oldval - COND_COUNTER_INCREMENT) & COND_COUNTER_MASK)
| flags;
if (__bionic_cmpxchg(oldval, newval, &cond->value) == 0)
break;
}
/*
* Ensure that all memory accesses previously made by this thread are
* visible to the woken thread(s). On the other side, the "wait"
* code will issue any necessary barriers when locking the mutex.
*
* This may not strictly be necessary -- if the caller follows
* recommended practice and holds the mutex before signaling the cond
* var, the mutex ops will provide correct semantics. If they don't
* hold the mutex, they're subject to race conditions anyway.
*/
ANDROID_MEMBAR_FULL();
__futex_wake_ex(&cond->value, COND_IS_SHARED(cond), counter);//这里传递过来的counter = 1;
return 0;
}
调用__futex_wake_ex来唤醒。
5. broadcast 唤醒所有等待mCond的线程
inline void Condition::broadcast() {
pthread_cond_broadcast(&mCond);
}
int pthread_cond_broadcast(pthread_cond_t *cond)
{
return __pthread_cond_pulse(cond, INT_MAX);//
INT_MAX是个超大值