Muduo(C++11版本) 源码剖析(三)———EventLoop设计

负责处理事件循环的关键类,遵循one loop per thread原则,所以一个线程中只有一个EventLoop对象,通过loop进入事件循环(while循环),在循环中主要有四个作用,按顺序是处理定时器事件,IO多路复用检测,处理检测返回的活动通道事件,处理自己的额外消息事件。one loop per thread已经是几乎所有网络库通用技术,需要深刻理解。

成员变量:

    private:
		typedef std::vector<Channel*> ChannelList;
		bool                                looping_;
		bool                                quit_;
		bool                                eventHandling_; 
		bool                                callingPendingFunctors_;
		const std::thread::id               threadId_;
		Timestamp                           pollReturnTime_;  
		std::unique_ptr<Poller>             poller_;          
                std::unique_ptr<TimerQueue>         timerQueue_;
                int64_t                             iteration_;
#ifdef _WIN64
                SOCKET                              wakeupFdSend_;
                SOCKET                              wakeupFdListen_;
                SOCKET                              wakeupFdRecv_;
#else
                SOCKET                              wakeupFd_;          
#endif
		std::unique_ptr<Channel>            wakeupChannel_;
		ChannelList                         activeChannels_;
		Channel*                            currentActiveChannel_;
		std::mutex                          mutex_;
		std::vector<Functor>                pendingFunctors_; 
		Functor                             frameFunctor_;

成员变量比较多,先简单讲下,下面将具体功能还会再讲一遍,looping是循环开启的标志位,quit是退出循环标志位,eventHandling_是正在处理IO事件标志位,callingPendingFunctors_是正在处理其他事件标志位,threadId是创建EventLoop的线程Id,pollReturnTime_是多路复用返回时间戳,poller_是封装IO多路复用的类,timerQueue是处理一些事件定时器队列,wakeup的三个socket是在windows下,该类给自己发送消息唤醒loop的Io复用,处理设置其他的事件,同样wakeupFd_是linux下的接口,wakeupChannel是处理自己事件的Channel,activeChannels是IO复用检测返回的活动通道集合,currentActiveChannel_是正在处理消息的Channel,pendingFunctors是自己设置的额外的处理函数集合。

 

重要函数及作用:

构造函数:

EventLoop::EventLoop()
    :looping_(false),
    quit_(false),
    eventHandling_(false),
    callingPendingFunctors_(false),
    threadId_(std::this_thread::get_id()),
    timerQueue_(new TimerQueue(this)),
    iteration_(0L),
    currentActiveChannel_(NULL)
{
    createWakeupfd();

#ifdef _WIN64
    wakeupChannel_.reset(new Channel(this, wakeupFdSend_)); //传入连接的socket
    poller_.reset(new SelectPoller(this));//Select模式

#else
    wakeupChannel_.reset(new Channel(this, wakeupFd_));
    poller_.reset(new EPollPoller(this));
#endif

    if (t_loopInThisThread)
    {
        LOGF("Another EventLoop  exists in this thread ");
    }
    else
    {
        t_loopInThisThread = this;
    }


    wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
    // we are always reading the wakeupfd
    wakeupChannel_->enableReading();

    //std::stringstream ss;	
    //ss << "eventloop create threadid = " << threadId_;
    //std::cout << ss.str() << std::endl;
}

EventLoop是在线程池分配的某一个线程中创建出来,不需要传入参数,在构造函数初始化列表中完成一些变量的初始化。我们可以看到函数体内主要的是创建了三个fd(linux只需要一个Fd)以及用一个活动通道注册他们,他们的作用我会在下面讲。我们还看到了一个plloer,这是封装了IO多路复用(selelct,poll,epoll)的类。

关键函数处理事件:

void EventLoop::loop()
{
    //assert(!looping_);
	//事件循环必须在io线程执行 如果不在代表发生错误,直接退出程序。
	assertInLoopThread();
    looping_ = true;
    quit_ = false;  // FIXME: what if someone calls quit() before loop() ?
    LOGD("EventLoop 0x%x  start looping", this);

    while (!quit_)
    {
		//定时器检测
        timerQueue_->doTimer();
        activeChannels_.clear();

		//调用poll返回活动的通道,有可能是唤醒返回的
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
        //if (Logger::logLevel() <= Logger::TRACE)
        //{
        printActiveChannels(); //打印下来
        //}
        ++iteration_;
        // TODO sort channel by priority
        eventHandling_ = true;
        for (const auto& it : activeChannels_)
        {
            currentActiveChannel_ = it;
            currentActiveChannel_->handleEvent(pollReturnTime_);
        }
        currentActiveChannel_ = nullptr;
        eventHandling_ = false;

		//这种设计使得IO线程也能执行一些计算任务,避免了IO线程在不忙时长期阻塞在IO multiplexing调用中
        doPendingFunctors();

        if (frameFunctor_)
        {
            frameFunctor_();
        }
    }

    LOGD("EventLoop 0x%0x stop looping", this);
    looping_ = false;

    std::ostringstream oss;
    oss << std::this_thread::get_id();
    std::string stid = oss.str();
    LOGI("Exiting loop, EventLoop object: 0x%x , threadID: %s", this, stid.c_str());
}

这是one loop per thread关键的函数了,可以看到实际上可以拆为四个作用,doTimer()处理定义的定时器事件,poll(kPollTimeMs, &activeChannels_)是通过IO复用技术检测有事件的通道,handleEvent(pollReturnTime_)活动通道处理返回的各个事件,doPendingFunctors()处理额外的一些任务。定时器队列这里不讲,自己有兴趣可以去了解。我们看到poll返回了所有有事件的activeChannels,之后Channel处理这些IO事件,当是侦听socket时,最终调用Acceptor::handleRead,当是客户端socket时,调用TcpConnection::handleRead。然后doPendingFunctors可以处理其他一些任务,比如客户端A给给客户端B发送消息,这当然执行在接受客户端A然后创建Session的线程中,我们在A的线程中找到了B的TcpConnection,进而找到了B的EventLoop,然后将这个发送任务加入B线程任务队列,也就是需要执行doPendingFunctors,但是程序阻塞在了poll返回函数里,执行不到doPendingFunctors,那么怎么办?于是我们给之前创建的唤醒fd发消息,唤醒poll,进而通过dpPendingFunctor处理发送消息,这里就是刚才创建唤醒的fd的作用。

给loop分派任务:

void EventLoop::runInLoop(const Functor& cb)
{
    if (isInLoopThread())//如果是本线程,则直接运行,否则加入队列,等待运行
    {
        cb();  //等于accept::listen
    }
    else
    {
        queueInLoop(cb);
    }
}

void EventLoop::queueInLoop(const Functor& cb)
{
    {
        std::unique_lock<std::mutex> lock(mutex_);
        pendingFunctors_.push_back(cb);
    }

    if (!isInLoopThread() || callingPendingFunctors_)
    {
        wakeup();
    }
}

和上面说的一样,外部通过runInloop给当前loop分配任务,在当前线程则直接执行,其他线程分到任务队列,然后唤醒fd,之后再处理。

其他的对Channel操作:

bool EventLoop::updateChannel(Channel* channel)
{
    //assert(channel->ownerLoop() == this);
    if (channel->ownerLoop() != this)
        return false;

    assertInLoopThread();

    return poller_->updateChannel(channel);
}

void EventLoop::removeChannel(Channel* channel)
{
    //assert(channel->ownerLoop() == this);
    if (channel->ownerLoop() != this)
        return;

    assertInLoopThread();
    if (eventHandling_)
    {
        //assert(currentActiveChannel_ == channel || std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
    }
    LOGD("Remove channel, channel = 0x%x, fd = %d", channel, channel->fd());
    poller_->removeChannel(channel);
}

bool EventLoop::hasChannel(Channel* channel)
{
    //assert(channel->ownerLoop() == this);
    assertInLoopThread();
    return poller_->hasChannel(channel);
}

重要的都已经讲完了,其他的还有一些对poll的注册活动通道的事件,没什么难点,看看就行了。

猜你喜欢

转载自blog.csdn.net/qq_36533978/article/details/107998831
今日推荐