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_;
};