muduo库源码阅读之MutexLock、MutexLockGuard、Contidion、CountDownLatch分析

一、互斥锁封装:Mutex.h

 github地址:https://github.com/chenshuo/muduo/blob/master/muduo/base/Mutex.h

 1.1 MutexLock类


互斥量是线程同步常用的变量,但在实际中一般都是使用封装的函数,这样便于操作。

MutexLock封装临界区(critical section),这是一个简单的资源类,用RAII手法封装互斥器的创建与销毁。

共有两个变量,mutex_是互斥量,holder_是用来表示给互斥量上锁线程的tid。
在构造函数中初始化互斥量mutex_和holder_(0),在析构函数中给销毁mutex_。对我接口根据名字很容易看出用法。
bool isLockByThisThread()是用来检查是否是当前线程给这个MutexLock对象加锁的,用于程序断言,原理为比较holder_和 CurrentThread::tid()的值。
assingnHolder和unassignHolder分别在上锁时给holder_赋值,解锁是给holder_置零。assignHolder在上锁后调用,而unassignHolder在解锁前调用。
void lock()和void unlock(),加锁和解锁, 仅供Conditio调用。在加锁时holder_被赋予 CurrentThread::tid(),解锁时被置0。
pthread_mutex_t* getPthreadMutex()可以返回指向类对象中互斥量的指针,在类外对互斥量操作,这个仅供Conditio调用。

有了MutexLockGuard为什么还要做一个UnassignGuard类?

这个 UnassignGuard类中有一个MutexLock对象的引用,在其构造函数调用unassignHolder,析构函数中assignHolder,这个是为条件变量 pthread_cond_wait()调用时设计的。
MutexLock::lock()里调用 assingnHolder()函数给holder_赋值,而MutexLock::unlock()里调用unassignHolder()函数给holder_置零。需要保持
MutexLock::holder_与pthread_mutex_t实例内部状态的一致性。在调用pthread_cond_wait()会解锁MuteLock,等待条件,解锁那就要给给holder_置零,pthread_cond_wait()返回后重新加锁同时要给给holder_赋值,否则MutexLock::holder_与pthread_mutex_t实例内部状态的一致性就被破坏了。所以需要在调用pthread_cond_wait的前后添加一些代码去相应的修改MutexLock::holder_,也就是分别调用MutexLock::unassignHolder和MutexLock::assignHolder。MutexLock::UnassignGuard类的作用,就是利用RAII简化对MutexLock::unassignHolder和MutexLock::assignHolder的调用。

在调用mutex的linux api时,检查返回值,如lock()函数。
void lock()
{
    MCHECK(pthread_mutex_lock(&mutex_));
    assignHolder();
}
检查返回值的意义在于防止ENOMEM 之类的资源不足情况,这一般只可能在负载很重的产品程序中出现。一旦出现这种错误,程序必须清理现场并主动退出,否则会莫名其妙的崩溃,给事后调查造成困难。可用non-debug的assert,或借鉴google-glog的CHECK()宏

定义的MCHECK宏 

#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

#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret);         \
                       assert(errnum == 0); (void) errnum;})

#endif // CHECK_PTHREAD_RETURN_VALUE

1.2  MutexLockGuard类


在使用mutex时,有时会忘记给mutex解锁,为了防止这种情况发生,常常使用RAII手法(资源的地点是构造函数,释放点是析构函数) 。MutexLockGuard就是为此设计的。
内部有一个MutexLock变量的引用。
MutexLockGuard封装临界区的进入和退出,即加锁和解锁。MutexLockGuard一般是个栈上对象,它的作用域刚好等于临界区域。通过传递Mutex引用,在构造函数中对其加锁; 在析构函数解锁。
// Prevent misuse like:
// MutexLockGuard(mutex_);
// A tempory object doesn't hold the lock for long!
#define MutexLockGuard(x) error "Missing guard object name"
定义了上面的一个宏,这个宏的作用是防止程序里出现如下错误:
void doit()
{
    MutexLockGuard(mutex);  //漏写变量名,产生一个临时对象又马上销毁了,结果没锁住临界区
    
    //正确写法是 MutexLockGuard lock(mutex);
    //临界区
}




二、条件变量的封装:Condition.h
github地址:

conditon构造函数中传递MutexLock的引用
void wait()
{
    MutexLock::UnassignGuard ug(mutex_);//这里先给mutex_的holder_置零。在等其析构时会给holder_赋值
    MCHECK(pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()));//给mutex_解锁
}
pthread_cond_wait(&pcond_, mutex_.getPthreadMutex())会先给mutex_解锁,然后等待条件。这两步是原子操作。在条件成立后,它会给mutex_加锁,然后返回,这两步也是原子操作。
在这里可以看到MutexLock::UnassignGuard的应用。
在执行等待之前,使用UnassignGuard的构造函数将mutex_的holder清空(因为当前线程会休眠,暂时失去对mutex_的所有权)。接着调用pthread_cond_wait等待其他线程的通知。当其他某个线程调用了notify/notifyAll时,当前线程被唤醒,接着在wait返回时,UnassignGuard的析构函数自动将mutex_的holder设置为当前线程。MutexLock::UnassignGuard类的作用,就是利用RAII简化对MutexLock::unassignHolder和MutexLock::assignHolder的调用。

时间函数: time( )--秒级, gettimeofday---微妙 , clock_gettime --纳秒 , _ftime ---毫秒
// returns true if time out, false otherwise.
bool muduo::Condition::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 = 1e9;
  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);
}
如果一个class要包含MutexLock和 Condition,请注意它们的声明顺序和初始化顺序,mutex_应该先于condition_构造,并作为后者的构造参数。


三、倒计时类封装:CountDownLatch
github地址:

这个是参考Java的一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。既可以用于所有子线程等待主线程发起 “起跑”,也可以用于主线程等待子线程初始化完毕才开始工作

condition 与mutex都为自己私有的,不是外边传进来的。
发号通知-报数的人,不断调用CountDownLatch::countDown( ) 减数,减到0,就通知其他等待的人 
等待命令的人,调用CountDownLatch::wait()等待
但是所有人都要调用同一个CountDownLatch示例(即统一condition、mutex)
注意mutext_是mutable类型,因为 getCount成员函数是const类型,不能修改此实例,但是getCount用到了锁,要修改mutex_,为了可以只修改锁,而不修改其他变量,所以把锁表明mutable类型

以一个例子说明,主线程创建了2个子线程,这两个子线程调用的函数阻塞在wait()上。当主线程调用countDown()后,子线程才运行。
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>
#include <boost/bind.hpp>

#include <vector>
#include <stdio.h>
using namespace muduo;
CountDownLatch latch_(1);
void Function()
{
    latch_.wait();//wait for latch_ countDown
    printf("Thread ID=%d, Name=%s\n", CurrentThread::tid(), CurrentThread::name());
}
int main()
{
    Thread t1(boost::bind(Function), "Thread 1");
    Thread t2(boost::bind(Function), "Thread 2");
    t1.start();
    t2.start();

    printf("main thread running, before countDown\n");
    latch_.countDown();
    sleep(3);//wait for thread t1, t2
    printf("main thread running, after countDown\n");

    t1.join();
    t2.join();

    return 0;
}
运行结果:
main thread running, before countDown
Thread ID=6994, Name=Thread 2
Thread ID=6993, Name=Thread 1
main thread running, after countDown

在程序里使用Pthreads库有一个额外的好处:分析工具认得它们,懂得其语意。线程分析工具如Intel Thread Checker和Valgrind-Helgrind等能识别Pthreads调用,并依据happens-before关系分析程序有无data race.
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

X-Programer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值