在java中,我们通常通过volitile、synchronized关键字来保证变量、函数或代码段在多线程中数据的原子性。在使用C进行linux编程时,我们需要用到以下函数:
//互斥锁上锁
pthread_mutex_lock
//互斥锁解锁
pthread_mutex_unlock
//动态创建条件变量
pthread_cond_init
//等待条件变量,挂起线程,区别是后者,会有timeout时间,
//如果到了timeout,线程自动解除阻塞,这个时间和 time()系统调用相同意义的。以1970年时间算起
pthread_cond_wait / pthread_cond_timedwait
//激活等待列表中的线程,
pthread_cond_signal
//激活所有等待线程列表中最先入队的线程
pthread_cond_broadcast
//销毁互斥锁
pthread_mutex_destroy
//销毁条件锁
pthread_cond_destroy
下面我们来总结不同场景下要怎么使用上面的函数。
最普通的阻塞锁
pthread_mutex_t mutex_;
pthread_mutex_init(&mutex_, NULL);
pthread_mutex_lock(&mutex_);
//这里执行需要多线程同步的代码
...
pthread_mutex_unlock(&mutex_);
pthread_mutex_destroy(&mutex_);
带条件等待的锁
pthread_mutex_t mutex_;
pthread_mutex_init(&mutex_, NULL);
pthread_cond_t cond_;
pthread_cond_init(&cond_, NULL);
//~~~~~A函数~~~~~~~~~
pthread_mutex_lock(&mutex_);
//这里执行需要多线程同步的代码
...
//等待唤醒
pthread_cond_wait(&cond_, &mutex_);
pthread_mutex_unlock(&mutex_);
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&mutex_);
//~~~~~B函数~~~~~~~~~
//唤醒A函数的pthread_cond_wait,使其继续往下执行
pthread_cond_signal(&cond_);
需要注意,条件等待锁pthread_cond_wait需要配合pthread_mutex_lock使用!因为多个线程同时请求pthread_cond_wait会产生死锁。
调用pthread_cond_wait后,会首先对入参的pthread_mutex_t执行解锁,以使其他线程可以进行操作,最终使得条件成立,解除条件锁定。同时还会对pthread_cond_t加锁,此时线程挂起不占用CPU周期。当通过pthread_cond_signal对pthread_cond_t解锁后,pthread_cond_wait函数又会对pthread_mutex_t执行加锁!简而言之,这个函数会涉及解锁-等待条件信号-加锁三个过程。
android对线程锁的懒人封装
在system/core/include/utils/Mutex.h中定义:
typedef Mutex::Autolock AutoMutex;
Mutex和Autolock同样在Mutex.h中,定义如下:
class Mutex {
public:
enum {
PRIVATE = 0,
SHARED = 1
};
Mutex();
explicit Mutex(const char* name);
explicit 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 defined(__ANDROID__)
// Lock the mutex, but don't wait longer than timeoutNs (relative time).
// Returns 0 on success, TIMED_OUT for failure due to timeout expiration.
//
// OSX doesn't have pthread_mutex_timedlock() or equivalent. To keep
// capabilities consistent across host OSes, this method is only available
// when building Android binaries.
//
// FIXME?: pthread_mutex_timedlock is based on CLOCK_REALTIME,
// which is subject to NTP adjustments, and includes time during suspend,
// so a timeout may occur even though no processes could run.
// Not holding a partial wakelock may lead to a system suspend.
status_t timedLock(nsecs_t timeoutNs);
#endif
// Manages the mutex automatically. It'll be locked when Autolock is
// constructed and released when Autolock goes out of scope.
class Autolock {
public:
//构造时加锁
inline explicit Autolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); }
inline explicit 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(_WIN32)
pthread_mutex_t mMutex;
#else
void _init();
void* mState;
#endif
};
//构造时初始化同步锁
inline Mutex::Mutex() {
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);
}
结合上面的代码,说说为什么Autolock是懒人封装:
首先Mutex的构造和析构帮我们完成了同步锁初始化和销毁,而Autolock的构造和析构进一步帮我们完成了加锁和解锁的过程!我们知道,对于函数中的局部变量来说,函数执行完成,就会被销毁,所以,我们可以这样通过Autolock实现线程同步:
void func() {
//锁初始化
Mutex mLock;
//加锁
AutoMutex _l(mLock);
//这里执行需要多线程同步的代码
...
}
我们不用再手动调用pthread_mutex相关初始化、加锁解锁和销毁函数了,偷懒不。
注意,mLock可以定义成成员变量重复使用,但AutoMutex 的对象必须是局部变量。
android对条件锁的懒人封装
既然偷懒,那就懒到底。android同样对pthread_cond相关函数进行了封装,在/system/core/include/utils/Condition.h
因为封装的方法和Autolock基本一致,这里就不贴代码了,使用实例如下:
class Barrier
{
public:
inline Barrier() : state(CLOSED) { }//state就是所谓的“条件”
inline ~Barrier() { }
void open() {
Mutex::Autolock _l(lock);
state = OPENED;
cv.broadcast();
}
void close() {
Mutex::Autolock _l(lock);
state = CLOSED;
}
void wait() const {
Mutex::Autolock _l(lock);//临时对象_l,用lock来构造,在AutoLock的构造函数里已给lock加锁(调用lock()函数)——该wait()函数执行完毕,会自动释放lock(这个场景会使得其他线程再次修改state,产生不安全因素。不过由于Barrier的使用场景的特殊性,其用在线程初始化时,故OK。)
while (state == CLOSED) {//循环,直到state==OPENED
cv.wait(lock);
}
}
private:
enum { OPENED, CLOSED };
mutable Mutex lock;//持有一个互斥锁
mutable Condition cv;//持有一个条件变量
volatile int state;//每次都从内存更新的“条件”
};
Condition和Mutex一样,可以定义为成员变量重复使用。