muduo源码学习笔记(1)

前言:

​ 对于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__)

​ 最后会按照FILELINE : 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::unassignHolderMutexLock::assignHolderMutexLock::UnassignGuard类的作用,就是利用RAII简化对MutexLock::unassignHolderMutexLock::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_;
    };

猜你喜欢

转载自blog.csdn.net/hyj_zkdzslh/article/details/82595348
今日推荐