前言:
对于muduo库,我觉得,光Linux多线程上提到的一些实现,还是不够的,在base/里面,还有/net里面提供了很多不错的实现,值得去学习,暑假算是看看muduo的百分之八十的源码,并对其进行了一次实现,(剩下的在最近总结的时候,也会开始看看,并实现一遍),对于muduo库,简单谈谈自己对其实现的理解。
用RAII管理你的锁
Posix Thread内定义的一系列的mutex函数,但是是基于C语言的,用起来很不方便,需要不断地初始化,销毁p,我们要对其进行一定程度的封装,使得我们不必重复的写初始话,销毁操作,专注在使用上。
muduo对锁进行了封装,分别是Mutex、MutexGruad、Condition、CountDownLatch。
Mutex的实现
class MutexLock : boost::noncopyable
{
public:
MutexLock():holder_(0)
{
MCHECK(pthread_mutex_init(&mutex_, NULL));
}
~MutexLock()
{
assert(holder_ == 0);
MCHECK(pthread_mutex_destroy(&mutex_));
}
bool isLocketByThisThread() const
{
return holder_ == CurrentThread::tid();
}
void assertLocked() const
{
assert(isLocketByThisThread());
}
void lock()
{
MCHECK(pthread_mutex_lock(&mutex_));
assignHolder();
}
void unlock()
{
unassignHolder();
MCHECK(pthread_mutex_unlock(&mutex_));
}
pthread_mutex_t* getPthreadMutex() /* non-const */
{
return &mutex_;
}
private:
void unassignHolder()
{
holder_ = 0;
}
void assignHolder()
{
holder_ = CurrentThread::tid();
}
private:
pthread_mutex_t mutex_;
pid_t holder_; // 如果不是同一个线程的加锁和解锁则会失败
};如果单单只是这样实现mutex,还是太过于麻烦,而且这个锁,我们只能在函数内自动获取自动产生,然后自动析构,会有一定的性能损失。
下面实现了MutexLockGurad用于自动获取锁,加锁和解锁。
class MutexLockGuard : boost::noncopyable
{
public:
explicit MutexLockGuard(MutexLock& mutex):mutex_(mutex)
{
mutex_.lock();
}
~MutexLockGuard()
{
mutex_.unlock();
}
private:
MutexLock& mutex_;
};还有需要注意的就是,在实现的时候,定义了两个宏
// 用宏的方式检查返回值是否为0,
#ifdef CHECK_PTHREAD_RETURN_VALUE
#ifdef NDEBUG
__BEGIN_DECLS
extern void __assert_perror_fail (int errnum,
const char *file,
unsigned int line,
const char *function)
__THROW __attribute__ ((__noreturn__));
__END_DECLS
#endif
#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret); \
if (__builtin_expect(errnum != 0, 0)) \
__assert_perror_fail (errnum, __FILE__, __LINE__, __func__);})
#else // CHECK_PTHREAD_RETURN_VALUE
// 定义一个ret类型的errnum,然后判断errnum是否==0,
// (void)(value)作用仅仅就是以显眼的方式让编译器不要给出参数未被使用的警告
#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret); \
assert(errnum == 0); (void) errnum;})
#endif // CHECK_PTHREAD_RETURN_VALUE
// 防止错误的用法
#define MutexLockGuard(x) error "Missing guard object name"没有注释的代码,里面有些小问题,我们可以总结一下
__builtin_expect
提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降。
函数声明如下
long __builtin_expect(long exp, long c);
exp 为一个整型表达式, 例如: (ptr != NULL)
c 必须是一个编译期常量, 不能使用变量
返回值等于 第一个参数 exp
这个函数的语义是:你期望exp表达式的值等于常量c,从而GCC为你优化程序,将符合这个条件的分支放在合适的地方。
__assert_perror_fail (errnum, __FILE__, __LINE__, __func__)
最后会按照FILE :LINE : func : errnum打印出错误,和assert一样,都在编译时候执行,但是好处在于它能按照格式打印出错误在哪里,便于快速排查出错误。
Condition的实现
如果直接使用我们上面实现的Mutex,会出现死锁。
因为在我们使用Conditon的wait的时候:
释放Mutex
阻塞等待
当其它线程调用pthread_cond_signal或pthread_cond_signal,它会重写获取锁。
这个时候,pthread_cond_wait势必会改变pthread_mutex_t和MutexLock:holder的一致性。所以需要在调用pthread_cond_wait的前后添加一些代码去相应的修改*MutexLock::holder,也就是分别调用MutexLock::unassignHolder和MutexLock::assignHolder。MutexLock::UnassignGuard类的作用,就是利用RAII简化对MutexLock::unassignHolder和MutexLock::assignHolder的调用。*
所以在Mutex中我们需要加入unassignGuard类
class Mutex { ... private: friend class Condition; class UnassignGuard : boost::noncopyable { public: UnassignGuard(MutexLock& owner) : owner_(owner) { owner_.unassignHolder(); } ~UnassignGuard() { owner_.assignHolder(); } private: MutexLock& owner_; }; ... };这样我们就可以实现Conditon了
class Condition : boost::noncopyable { public: explicit Condition(MutexLock& mutex) : mutex_(mutex) { MCHECK(pthread_cond_init(&pcond_, NULL)); } ~Condition() { MCHECK(pthread_cond_destroy(&pcond_)); } void wait() { MutexLock::UnassignGuard ug(mutex_); //先将holder_清零,防止出现死锁 MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex())); // 析构的时候,会将holder_恢复 } // returns true if time out, false otherwise. bool waitForSeconds(double seconds); void notify() { MCHECK(pthread_cond_signal(&pcond_)); } void notifyAll() { MCHECK(pthread_cond_broadcast(&pcond_)); } void waitForSeconds(double seconds) { struct timespec abstime; // FIXME: use CLOCK_MONOTONIC or CLOCK_MONOTONIC_RAW to prevent time rewind. clock_gettime(CLOCK_REALTIME, &abstime); const int64_t kNanoSecondsPerSecond = 1000000000; int64_t nanoseconds = static_cast<int64_t>(seconds * kNanoSecondsPerSecond); abstime.tv_sec += static_cast<time_t>((abstime.tv_nsec + nanoseconds) / kNanoSecondsPerSecond); abstime.tv_nsec = static_cast<long>((abstime.tv_nsec + nanoseconds) % kNanoSecondsPerSecond); MutexLock::UnassignGuard ug(mutex_); return ETIMEDOUT == pthread_cond_timedwait(&pcond_, mutex_.getPthreadMutex(), &abstime); } private: MutexLock& mutex_; pthread_cond_t pcond_; }; CountDownLatch的实现
我们有时对Condition类上面再套一层封装来使用,他就是我们的CountDownLatch类。通过倒计时计数器的方式,设置计数。倒计时未完,CountDownLatch的内部一直处于
Condition类的wait()状态。倒计时完毕,唤醒cond,程序正常执行。所以我们可以给某件即将发生的事设置一个条件,比如至少5个满足才会触发该事件,那么计数值设为5,
等到倒计时为0时,CountDownLatch类就可以让你顺利的去执行该事件了。
所以它主要有两个用法:
1.既可以用于所有子线程主动等待主线程发起“起跑”
2.也可以用于主线哼等待子线程初始化完毕才开始工作
class CountDownLatch : boost::noncopyable { public: explicit CountDownLatch(int count) : mutex_(), condition_(mutex_), count_(count) { } void wait() { MutexLockGuard lock(mutex_); while (count_ > 0) { condition_.wait(); } } void countDown() { MutexLockGuard lock(mutex_); --count_; if (count_ == 0) { condition_.notifyAll(); } } int getCount() const { MutexLockGuard lock(mutex_); return count_; } private: mutable MutexLock mutex_; Condition condition_; int count_; };