android系统核心机制 基础(03)Thread 同步机制

198 篇文章 97 订阅

该系列文章总纲链接: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函数的使用,它们的用法如下:

  1. Lock用来加锁,unLock用来解锁,一般成对使用;
  2. 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的原理:

流程说明:

  1. 先定义一个Mutex,如 Mutex xlock;
  2. 在使用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++操作为例,它生成的汇编指令可能就是以下三条:

  1. 从内存中取数据到寄存器。
  2. 对寄存器中的数据进行递增操作,结果还在寄存器中。
  3. 寄存器的结果写回内存。

这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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

图王大胜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值