本文地址:http://blog.csdn.net/freeelinux/article/details/53510541
首先来看一下一个进程(线程)如何通知另一个个等待中的(线程),有三种方法:
1.pipe,使用fd[0] 为读端,fd[1]为写端,半双工。等待线程关注fd[0]的可读事件。
2.socketpair,也有一对文件描述符,可用于双向通信,全双工。
3.eventfd。eventfd是一个比pipe更搞笑的线程间事件通知机制,一方面它比pipe少用一个pipe descriptor,节省了资源。另一方面,eventfd的缓冲区管理也简单的多,全部“buffer"
只有定长8bytes,不想pipe那样可能有不定长的真正buffer。
实际上线程还可以使用条件变量condition来实现消息通信。
muduo采用的就是eventfd,来看一下它的函数:
#include
int eventfd(unsigned int initval, int flags);
第一个参数是初始值,一般设置为0,后面是O_NONBLOCK之类的标志。
简单来说eventfd就是一个文件描述符,它引用了一个内核维护的eventfd object,是uint64_t类型,也就是8个字节,可以作为counter。支持read,write,以及有关epoll等操作。
该值不为0,read就可以读了。
关于eventfd更多内容参考我的博客:http://blog.csdn.net/freeelinux/article/details/53511331
二:EventLoop剖析
EventLoop是整个muduo网络库的核心类,其重要性不言而喻。首先来看一下它的类图:
图片来自:http://blog.csdn.net/KangRoger/article/details/47266785
我们来看一下EventLoop的主要成员:
typedef std::vector ChannelList;
bool looping_; /* atomic */ //loop循环标志
bool quit_; /* atomic and shared between threads, okay on x86, I guess. */ //是否退出标志
bool eventHandling_; /* atomic */ //是否在处理事件标志
bool callingPendingFunctors_; /* atomic */ //是否调用pendingFunctors标志
int64_t iteration_; //迭代器
const pid_t threadId_; //当前所属对象线程id
Timestamp pollReturnTime_; //时间戳,poll返回的时间戳
boost::scoped_ptr poller_; //poller对象
boost::scoped_ptr timerQueue_; //TimerQueue类型对象指针,构造函数中new
int wakeupFd_; //用于eventfd,线程间通信
// unlike in TimerQueue, which is an internal class,
// we don't expose Channel to client.
boost::scoped_ptr wakeupChannel_; //wakeupfd所对应的通道,该通道会纳入到poller来管理
boost::any context_; //暂未剖析
// scratch variables
ChannelList activeChannels_; //Poller返回的活动通道,vector类型
Channel* currentActiveChannel_; //当前正在处理的活动通道
MutexLock mutex_;
std::vector pendingFunctors_; // GuardedBy mutex_ //本线程或其它线程使用queueInLoop添加的任务,可能是I/O计算任务
EventLoop::EventLoop()
: looping_(false), //表示还未循环
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
iteration_(0),
threadId_(CurrentThread::tid()), //赋值真实id
poller_(Poller::newDefaultPoller(this)), //构造了一个实际的poller对象
timerQueue_(new TimerQueue(this)), //构造一个timerQueue指针,使用scope_ptr管理
wakeupFd_(createEventfd()), //创建eventfd作为线程间等待/通知机制
wakeupChannel_(new Channel(this, wakeupFd_)), //创建wakeupChannel通道,
currentActiveChannel_(NULL)
{
LOG_DEBUG << "EventLoop created " << this << " in thread " << threadId_;
if (t_loopInThisThread) //每个线程最多一个EventLoop对象,如果已经存在,使用LOG_FATAL终止abort它。
{
LOG_FATAL << "Another EventLoop " << t_loopInThisThread
<< " exists in this thread " << threadId_;
}
else
{
t_loopInThisThread = this; //this赋给线程局部数据指针,凭借这个这以保证per thread a EventLoop
}
//设定wakeupChannel的回调函数,即EventLoop自己的的handleRead函数
wakeupChannel_->setReadCallback(
boost::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading();
}
//事件循环,不能跨线程调用
//只能在创建该对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);
assertInLoopThread(); //断言处于创建该对象的线程中
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear(); //首先清零
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); //调用poll返回活动的通道,有可能是唤醒返回的
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
printActiveChannels(); //日志登记,日志打印
}
// TODO sort channel by priority
eventHandling_ = true; //true
for (ChannelList::iterator it = activeChannels_.begin();
it != activeChannels_.end(); ++it) //遍历通道来进行处理
{
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL; //处理完了赋空
eventHandling_ = false; //false
//I/O线程设计比较灵活,通过下面这个设计也能够进行计算任务,否则当I/O不是很繁忙的时候,这个I/O线程就一直处于阻塞状态。
//我们需要让它也能执行一些计算任务
doPendingFunctors(); //处理用户回调任务
}
LOG_TRACE << "EventLoop " << this << " stop looping";
looping_ = false;
}
wait函数中启用Poller开始wait,等待事件发生,也就是activeChannels_返回。返回后然后挨个处理发生的事件,调用每个返回的Channel的handle_event()函数。
//处理所有发生的事件,如果活着,底层调用handleEventWithGuard
void Channel::handleEvent(Timestamp receiveTime) //事件到来调用handleEvent处理
{
boost::shared_ptr guard;
if (tied_)
{
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
这里使用了shared_ptr保证线程安全,保证不会调用一个已经销毁了的对象,如果活着(此处具体解释待更新),内部调用handleEventWithGuard()函数处理细节。
//处理所有发生的事件
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) //判断返回事件类型
{
if (logHup_) //如果有POLLHUP事件,输出警告信息
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_(); //调用关闭回调函数
}
if (revents_ & POLLNVAL) //不合法文件描述符,并没有终止,因为服务器程序要保证一天二十四小时工作。
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) //POLLRDHUP是对端关闭连接事件,如shutdown等
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false; //处理完了=false
}
doPendingFunctors(); //
//顾名思义,在I/O线程中调用某个函数,该函数可以跨线程调用
void EventLoop::runInLoop(const Functor& cb)
{
//如果是在当前I/O线程中调用,就同步调用cb回调函数
if (isInLoopThread())
{
cb();
}
else
{
//否则在其他线程中调用,就异步将cb添加到任务队列当中,以便让EventLoop真实对应的I/O线程执行这个回调函数
queueInLoop(cb);
}
}
实际上就是如果是I/O线程主动调用该函数想要执行,那就同步执行该函数。如果是其他线程施加给I/O线程的任务,那么其他线程就需要把回调函数加入I/O线程的队列,等待异步执行。
//将任务添加到队列当中,队就是成员pendingFunctors_数组容器
void EventLoop::queueInLoop(const Functor& cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(cb); //添加到任务队列当中
}
//如果当前调用queueInLoop调用不是I/O线程,那么唤醒该I/O线程,以便I/O线程及时处理。
//或者调用的线程是当前I/O线程,并且此时调用pendingfunctor,需要唤醒
//只有当前I/O线程的事件回调中调用queueInLoop才不需要唤醒
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
//唤醒EventLoop
void EventLoop::wakeup()
{
uint64_t one = 1;
ssize_t n = sockets::write(wakeupFd_, &one, sizeof one); //随便写点数据进去就唤醒了
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
//设定wakeupChannel的回调函数,即EventLoop的handleRead函数
wakeupChannel_->setReadCallback(
boost::bind(&EventLoop::handleRead, this));
// we are always reading the wakeupfd
wakeupChannel_->enableReading();
void enableReading() { events_ |= kReadEvent; update(); } //或上事件,就是关注可读事件,注册到EventLoop,通过它注册到Poller中
void Channel::update() //更新事件类型
{
addedToLoop_ = true;
loop_->updateChannel(this); //调用loop的,loop再调用poll的注册pollfd
}
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
实际上调用的就是构造函数中注册的EventLoop::headleRead函数:
//实际上是wakeFd_的读回调函数
void EventLoop::handleRead()
{
uint64_t one = 1;
ssize_t n = sockets::read(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::handleRead() reads " << n << " bytes instead of 8";
}
}
doPendingFunctors(); //处理用户回调任务
// 1. 不是简单的在临界区内依次调用functor,而是把回调列表swap到functors中,这一方面减小了
//临界区的长度,意味着不会阻塞其他线程的queueInLoop(),另一方面也避免了死锁(因为Functor可能再次调用quueInLoop)
// 2. 由于doPendingFunctors()调用的Functor可能再次调用queueInLoop(cb),这是queueInLoop()就必须wakeup(),否则新增的cb可能就不能及时调用了
// 3. muduo没有反复执行doPendingFunctors()直到pendingFunctors为空,这是有意的,否则I/O线程可能陷入死循环,无法处理I/O事件
void EventLoop::doPendingFunctors()
{
std::vector functors;
callingPendingFunctors_ = true;
//注意这里的临界区,这里使用了一个栈上变量functors和pendingFunctors交换
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_); //把它和空vector交换
}
//此处其它线程就可以往pendingFunctors添加任务
//调用回调任务
//这一部分不用临界区保护
for (size_t i = 0; i < functors.size(); ++i)
{
functors[i]();
}
callingPendingFunctors_ = false;
}
它为了防止死锁与兼备高效性所采取的措施注释中已经解释过了,此刻I/O线程就可以顺利的执行这么些任务了。