该系列文章总纲链接:android 系统核心机制基础 系列文章目录
本章关键点总结 & 说明:
这里主要关注➕ Thread类的同步机制即可,主要解读了Mutex、Condition、原子操作这三种Sync机制。
同步相关的类在android系统中有Mutex&Condition&原子操作,本质是对系统提供的多线程同步函数(这种函数我们也称之为Raw API)进行了面向对象的封装。同时OS还提供了简单的原子操作,这些也算是同步技术的一种。接下来分别介绍这3种机制
1 Mutex操作
Mutex类的声明如下所示:
class Mutex{
public:
enum {
PRIVATE = 0,
SHARED = 1,
};
Mutex();
Mutex(const char* name);
Mutex(int type, const char* name = NULL);
~Mutex();
// lock or unlock the mutex
status_t lock();
void unlock();
// lock if possible; returns 0 on success, error otherwise
status_t tryLock();
#if HAVE_ANDROID_OS
status_t timedLock(nsecs_t timeoutMilliseconds);
#endif
class Autolock {
public:
inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
inline ~Autolock() { mLock.unlock(); }
private:
Mutex& mLock;
};
private:
friend class Condition;
// A mutex cannot be copied
Mutex(const Mutex&);
Mutex& operator = (const Mutex&);
#if defined(HAVE_PTHREADS)
pthread_mutex_t mMutex;
#else
void _init();
void* mState;
#endif
};
1.1 Mutex的关键实现代码如下所示(主要针对Lock,unLock,tryLock方法):
#if defined(HAVE_PTHREADS)
inline Mutex::Mutex() {
pthread_mutex_init(&mMutex, NULL);
}
inline Mutex::Mutex(__attribute__((unused)) const char* name) {
pthread_mutex_init(&mMutex, NULL);
}
inline Mutex::Mutex(int type, __attribute__((unused)) const char* name) {
if (type == SHARED) { //注意:type如果是SHARED,则表明这个Mutex支持跨进程的线程同步,一般默认是不支持的
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);
}
#if HAVE_ANDROID_OS
inline status_t Mutex::timedLock(nsecs_t timeoutNs) {
const struct timespec ts = {
/* .tv_sec = */ timeoutNs / 1000000000,
/* .tv_nsec = */ timeoutNs % 1000000000,
};
return -pthread_mutex_timedlock(&mMutex, &ts);
}
#endif
#endif // HAVE_PTHREADS
关于Mutex的使用,除了初始化外,最重要的是lock和unlock以及tryLock函数的使用,它们的用法如下:
- Lock用来加锁,unLock用来解锁,一般成对使用;
- tryLock:尝试加锁,如果成功与unLock成对,如果失败则什么都不做(成功失败通过返回值来判断)
注意:这里的Mutex类只是C++层面的封装。
1.2 AutoLock的使用(Mutex类的一个内部类)
Mutex类比Raw API方便好用,不过还是稍显麻烦。因此有了AutoLock的用法;Lock、unLock这套机制的缺陷,当显示调用Mutex的lock,在某个时候要记住调用该Mutex的unlock。这2个操作都必须一一对应,否则会出现“死锁”有些代码中,在判断分支特别多的情况下,unlock这句代码被写得比比皆是,如稍有不慎,在某处就会忘 写了它。有什么好办法能解决这个问题吗?终于有人想出来一个好办法,AutoLock就是充分利用了C++的构造和析构函数,因此,才有了AutoLock这套新的机制。AutoLock的代码如下所示:
class Autolock {
public:
inline Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } //构造的时候调用lock
inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
inline ~Autolock() { mLock.unlock(); } //析构的时候调用unlock
private:
Mutex& mLock;
};
1.3 AutoLock的原理:
流程说明:
- 先定义一个Mutex,如 Mutex xlock;
- 在使用xlock的地方,定义一个AutoLock,如 AutoLock autoLock(xlock)。
由于C++对象的构造和析构函数都是自动被调用的,所以在AutoLock的生命周期内,xlock的lock和unlock也就自动被调用了,这样就省去了重复书写unlock的麻烦,而且lock和unlock的调用肯定是一一对应的,这样就绝对不会出错。如果一个Autolock对象是局部变量,则在生命周期结束时就自动的把资源锁解了。
1.4 AutoLock的一个使用实例
void Thread::requestExit()
{
Mutex::Autolock _l(mLock);
mExitPending = true;
}
变量_l就是一个Autolock对象,它在构造时会主动调用requestExit中的lock锁。
当mExitPending = true;运行结束时,_l的生命周期也随之完结,于是lock所对应的锁也会被打开。
这是一个实现上的小技巧,在某些情况下可以有效防止开发人员没有配套使用lock/unlock。
2 Condition操作
多线程同步中的条件类对应的是下面一种使用场景:线程A做初始化工作,而其他线程比如线程B、C必须等到初始化工作完后才能工作,即线程B、C在等待一个条件,我们称B、C为等待者。当线程A完成初始化工作时,会触发这个条件,那么等待者B、C就会被唤醒。触发这个条件的A就是触发者。这里查看Condition的声明,代码如下:
class Condition {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
enum WakeUpType {
WAKE_UP_ONE = 0,
WAKE_UP_ALL = 1
};
Condition();
Condition(int type); //如果type是SHARED,表示支持跨进程的条件同步
~Condition();
//线程B和C等待事件
status_t wait(Mutex& mutex);
//线程B和C的超时等待,B和C可以指定等待时间,当超过这个时间,条件却还不满足,则退出等待
status_t waitRelative(Mutex& mutex, nsecs_t reltime);
void signal(); //触发者A用来通知条件已经满足,但是B和C只有一个会被唤醒
void broadcast();//触发者A用来通知条件已经满足,所有等待者都会被唤醒
void signal(WakeUpType type) {//一层封装 type为WakeUpType类型,为更多开发者提供便利
if (type == WAKE_UP_ONE) {
signal();
} else {
broadcast();
}
}
private:
#if defined(HAVE_PTHREADS)
pthread_cond_t mCond;
#else
void* mState;
#endif
};
2.1 查看Condition的实现,代码如下:
#if defined(HAVE_PTHREADS)
inline Condition::Condition() {
pthread_cond_init(&mCond, NULL);
}
inline Condition::Condition(int type) {
if (type == SHARED) {//设置跨进程的同步支持
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&mCond, &attr);
pthread_condattr_destroy(&attr);
} else {
pthread_cond_init(&mCond, NULL);
}
}
inline Condition::~Condition() {
pthread_cond_destroy(&mCond);
}
inline status_t Condition::wait(Mutex& mutex) {
return -pthread_cond_wait(&mCond, &mutex.mMutex);
}
inline status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) {
//有些系统没有实现POSIX的相关函数,所以不同系统需要调用不同的函数
//因此以下宏的判断比较多
#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
struct timespec ts;
ts.tv_sec = reltime/1000000000;
ts.tv_nsec = reltime%1000000000;
return -pthread_cond_timedwait_relative_np(&mCond, &mutex.mMutex, &ts);
#else // HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE
struct timespec ts;
#if defined(HAVE_POSIX_CLOCKS)
clock_gettime(CLOCK_REALTIME, &ts);
#else // HAVE_POSIX_CLOCKS
// we don't support the clocks here.
struct timeval t;
gettimeofday(&t, NULL);
ts.tv_sec = t.tv_sec;
ts.tv_nsec= t.tv_usec*1000;
#endif // HAVE_POSIX_CLOCKS
ts.tv_sec += reltime/1000000000;
ts.tv_nsec+= reltime%1000000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_nsec -= 1000000000;
ts.tv_sec += 1;
}
return -pthread_cond_timedwait(&mCond, &mutex.mMutex, &ts);
#endif // HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE
}
inline void Condition::signal() {
pthread_cond_signal(&mCond);
}
inline void Condition::broadcast() {
pthread_cond_broadcast(&mCond);
}
#endif // HAVE_PTHREADS
Condition类的实现只是封装了Raw API的pthread_cond_xxx函数。且Condition类必须配合Mutex来使用。以上代码中,不论是wait、waitRelative、signal还是broadcast的调用,都放在一个Mutex的lock和unlock范围中,尤其是wait和waitRelative函数的调用,这是强制性的。
2.2 Condition实例
主要是加深对Condition类和Mutex类使用的理解,Thread类的requestExitAndWait,目的是等待工作线程退出)代码如下所示:
status_t Thread::requestExitAndWait()
{
Mutex::Autolock _l(mLock);//使用Autolock,mLock被锁住
if (mThread == getThreadId()) {
return WOULD_BLOCK;
}
mExitPending = true;
while (mRunning == true) {//条件变量的等待,因为某些时候即使条件类没有被触发,wait也会返回。
mThreadExitedCondition.wait(mLock);
}
mExitPending = false;
//退出前,局部变量Mutex::Autolock _l的析构会被调用,unlock也就会被自动调用。
return mStatus;
}
在工作线程退出前触发这个Condition。其代码如下所示:
int Thread::_threadLoop(void* user)
{
Thread* const self = static_cast<Thread*>(user);
sp<Thread> strong(self->mHoldSelf);
wp<Thread> weak(strong);
self->mHoldSelf.clear();
#ifdef HAVE_ANDROID_OS
self->mTid = gettid();
#endif
bool first = true;
do {
...
//如果mExitPending为true,则退出
{//注意:AutoLock的有效范围是代码块
Mutex::Autolock _l(self->mLock);
if (result == false || self->mExitPending) {
self->mExitPending = true;
self->mRunning = false;//mRunning的修改位于锁的保护中
self->mThread = thread_id_t(-1);
self->mThreadExitedCondition.broadcast(); //退出前触发条件变量,唤醒所有等待者
break;
}
}
strong.clear();
strong = weak.promote();
} while(strong != 0);
return 0;
}
3 原子操作
原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,原子操作是最小的执行单位。
3.1 这里以实例驱动,关键代码如下:
static int g_flag = 0; //全局变量g_flag
static Mutex lock ;//全局的锁
//线程1执行thread1
void thread1()
{
//g_flag递减,每次操作前锁住
lock.lock();
g_flag--;
lock.unlock();
}
//线程2中执行thread2函数
void thread2()
{
lock.lock();
g_flag++; //线程2对g_flag进行递增操作,每次操作前要取得锁
lock.unlock();
}
特殊说明(使用Mutex的原因):g_flags++或者g_flags--操作都不是原子操作。同时从汇编指令的角度看,C/C++中的一条语句对应了数条汇编指令。以g_flags++操作为例,它生成的汇编指令可能就是以下三条:
- 从内存中取数据到寄存器。
- 对寄存器中的数据进行递增操作,结果还在寄存器中。
- 寄存器的结果写回内存。
这3条汇编指令,如果按正常的顺序连续执行是没有问题的,但在多线程时就无法保证。
例如:线程1在执行第一条指令后,线程2由于调度的原因,抢先在线程1之前连续执行完了三条指令。这样,线程1继续执行指令时,它所使用的值就不是线程2更新后的值,而是之前的旧值。再对这个值进行操作便没有意义了。
在一般情况下,处理这种问题可以使用Mutex来加锁保护,但Mutex的使用比它所要保护的内容还复杂,例如,锁的使用将导致从用户态转入内核态,有较大的浪费。那么这时就需要原子操作了(需要CPU的支持。目前主流平台均支持)
3.2 Android提供的相关原子操作函数。
//原子赋值操作,结果是*addr=value
void android_atomic_write(int32_t value,volatile int32_t* addr);
//下面所有函数的返回值都是操作前的旧值
//原子加1和原子减1
int32_t android_atomic_inc(volatile int32_t*addr);
int32_t android_atomic_dec(volatile int32_t*addr);
//原子加法操作,value为被加数,如果遇到减法,加负数就可以
int32_t android_atomic_add(int32_t value,volatile int32_t* addr);
//原子“与”和“或”操作
int32_t android_atomic_and(int32_t value,volatile int32_t* addr);
int32_t android_atomic_or(int32_t value,volatile int32_t* addr);
/*
条件交换的原子操作。只有在oldValue等于*addr时,才会把newValue赋值给*addr
这个函数的返回值须特别注意。返回值非零,表示没有进行赋值操作。返回值为零,表示
进行了原子操作。
*/
int android_atomic_cmpxchg(int32_t oldvalue,int32_t newvalue,volatile int32_t*addr);
原子操作的最大好处在于避免了锁的使用。简单的操作用原子操作就好,复杂的操作才用Mutex。