Boost.ASIO源码:pthread包装类——posix_event小结

posix_event介绍

该类用在scheduler中,用于唤醒阻塞的线程,下面代码中的conditionally_enabled_event实际上就是posix_event的包装类:

class scheduler
  : public execution_context_service_base<scheduler>,
    public thread_context
{
// ...

private// The event type used by this scheduler.
  typedef conditionally_enabled_event event;

// Event to wake up blocked threads.
  event wakeup_event_;
  
 // ...
}

至于为什么要用conditionally_enabled_event来包一层呢,主要是为了多加一个enabled这个flag。
以前也总结过,io_service能够根据传入的concurrency_hint决定是否采用多线程并行,若是不采用并行,则理所当然的,加锁就成了没必要的操作,为了对这里面涉及到的锁进行统一化处理,他们决定用一个enabled标志位来判断是否真的加锁。
而对于event,自然也是同理。如以下的conditionally:

  void clear(conditionally_enabled_mutex::scoped_lock& lock)
  {
    if (lock.mutex_.enabled_)  // 这里进行enabled控制
      event_.clear(lock);  // 调用posix_event的接口
  }

posix_event源码

posix_event结构也很简单,实际上就是pthread的包装。posix_event就只有两个成员变量:

class posix_event
  : private noncopyable
{
private:
  ::pthread_cond_t cond_;  // 条件变量
  std::size_t state_;
};

cond_很好理解,就是用来控制阻塞唤醒的条件变量。而state_是一个无符号整数,它的设计很巧妙,state_有2个作用:一是用来在wait中作为条件控制判断的flag;二是用来判断是否还有处于阻塞在该条件变量上的线程。下面详细说明。
以signal_all函数为例子:

  template <typename Lock>
  void signal_all(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    (void)lock;
    state_ |= 1;
    ::pthread_cond_broadcast(&cond_);
  }

BOOST_ASIO_ASSERT是宏函数,是一个断言判断,如果传入值为true则没事,传入值为false就抛出异常。在这里仅是为了检验是否已加锁。
第5行这种写法在Boost中出现了多次,没有实际作用,仅是为了欺骗编译器,不然编译器会报警告,说没有使用该变量。
第6行就是关键的state_的控制操作,在这里可以看出state_的最低位就是我们的flag标志位。结合wait函数来看逻辑可能会更清晰:

  // Wait for the event to become signalled.
  template <typename Lock>
  void wait(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    while ((state_ & 1) == 0)
    {
      state_ += 2;
      ::pthread_cond_wait(&cond_, &lock.mutex().mutex_); // Ignore EINVAL.
      state_ -= 2;
    }
  }

第5行的循环判断就用到了state的最低位,若该位为0,则说明还没有到达唤醒的条件,否则说明该线程已正常加锁唤醒。
至于为什么要用循环判断,这算是pthread_cond_wait的标准用法,因为处于等待状态的线程可能有多个,一次可能有多个线程被唤醒,而这里要保证进入临界区的线程只有一个。
再回到上面的wait函数中,可以看到在调用pthread_cond_wait进入阻塞状态之前,先将state_自增2。这里可以看出 state_ / 2 实际上就是当前处于等待状态的线程数了,此时无论最低位是否为0,也就是无论state是否为偶数都不影响。由此达到用一个无符号整数变量记录2个操作信息的目的。
在最后,还是附上posix_event的完整代码,我这里删除了许多条件编译相关的代码,该源码是基于Linux的。

class posix_event
  : private noncopyable
{
public:
  // Constructor.
  BOOST_ASIO_DECL posix_event();

  // Destructor.
  ~posix_event()
  {
    ::pthread_cond_destroy(&cond_);
  }

  // Signal the event. (Retained for backward compatibility.)
  template <typename Lock>
  void signal(Lock& lock)
  {
    this->signal_all(lock);
  }

  // Signal all waiters.
  template <typename Lock>
  void signal_all(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    (void)lock;
    state_ |= 1;
    ::pthread_cond_broadcast(&cond_); // Ignore EINVAL.
  }

  // Unlock the mutex and signal one waiter.
  template <typename Lock>
  void unlock_and_signal_one(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    state_ |= 1;
    bool have_waiters = (state_ > 1);
    lock.unlock();
    if (have_waiters)
      ::pthread_cond_signal(&cond_); // Ignore EINVAL.
  }

  // If there's a waiter, unlock the mutex and signal it.
  template <typename Lock>
  bool maybe_unlock_and_signal_one(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    state_ |= 1;
    if (state_ > 1)
    {
      lock.unlock();
      ::pthread_cond_signal(&cond_); // Ignore EINVAL.
      return true;
    }
    return false;
  }

  // Reset the event.
  template <typename Lock>
  void clear(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    (void)lock;
    state_ &= ~std::size_t(1);
  }

  // Wait for the event to become signalled.
  template <typename Lock>
  void wait(Lock& lock)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    while ((state_ & 1) == 0)
    {
      state_ += 2;
      ::pthread_cond_wait(&cond_, &lock.mutex().mutex_); // Ignore EINVAL.
      state_ -= 2;
    }
  }

  // Timed wait for the event to become signalled.
  template <typename Lock>
  bool wait_for_usec(Lock& lock, long usec)
  {
    BOOST_ASIO_ASSERT(lock.locked());
    if ((state_ & 1) == 0)
    {
      state_ += 2;
      timespec ts;

      if (::clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
      {
        ts.tv_sec += usec / 1000000;
        ts.tv_nsec = (usec % 1000000) * 1000;
        ts.tv_sec += ts.tv_nsec / 1000000000;
        ts.tv_nsec = ts.tv_nsec % 1000000000;
        ::pthread_cond_timedwait(&cond_,
            &lock.mutex().mutex_, &ts); // Ignore EINVAL.
      }

      state_ -= 2;
    }
    return (state_ & 1) != 0;
  }

private:
  ::pthread_cond_t cond_;
  std::size_t state_;
};

猜你喜欢

转载自blog.csdn.net/weixin_43827934/article/details/85093852