muduo源码分析——EventLoop

先简单说一下,因为我参考的是flamingo的代码,会和muduo有一点点出入,但是基本是差不多,因为前者是基于后者开发的,可能有一点点改动。

EventLoop是muduo里比较核心的类吧,因为它是实现Reactor模式的核心,什么是Reactor模式,其实很多文章和书籍都有讲,我也打算抽空写一篇博客,来总结下自己的心得,至少现在没有总结,哈哈~~。

其实如果认真读一下EventLoop里的Loop循环的话,就可以很好的了解Reactor模式了,
不过还是先从成员开始吧!

成员

    private:
		typedef std::vector<Channel*> ChannelList;

		bool                                looping_; /* atomic */
		bool                                quit_; /* atomic and shared between threads, okay on x86, I guess. */
		bool                                eventHandling_; /* atomic */
		bool                                callingPendingFunctors_; /* atomic */
		const std::thread::id               threadId_;//对应的线程id号
		Timestamp                           pollReturnTime_;
		std::shared_ptr<Poller>             poller_;//初始化默认创建EpollPoller
        std::shared_ptr<TimerQueue>         timerQueue_;
        int64_t                             iteration_;

        SOCKET                              wakeupFd_;          //TODO: 这个fd什么时候释放?,在EventLoop被析构的时候会释放,但是确实没有找到相应调用析构的代码。
		// unlike in TimerQueue, which is an internal class,
		// we don't expose Channel to client.
		std::shared_ptr<Channel>            wakeupChannel_;
	
		// scratch variables
		ChannelList                         activeChannels_;//epoll事件会被压入这个vector
		Channel*                            currentActiveChannel_;//被用来遍历,上一行的vector

		std::mutex                          mutex_;//用来保护下面那行的执行
		std::vector<Functor>                pendingFunctors_; // Guarded by mutex_

		Functor                             frameFunctor_;
	};

注意!为了减少行数,我把#indef win32给删掉了。
先简单说一下4个bool型的变量:
looping_代表是否开始循环。
quit_代表是否退出。
eventHandling_代表是否在处理事件。
callingPendingFunctors_代表是否在处理被push的待执行函数(此处后面会说一下)。

说一下这个东西

SOCKET      wakeupFd_;//TODO: 这个fd什么时候释放?,在EventLoop被析构的时候会释放,但是确实没有找到相应调用析构的代码。

这个socket使用来唤醒的,有一个wakeup函数,会向这个socket写入一个uint64_t one = 1;来唤醒它。不过这个释放,确实没有找到具体的实现位置,它会在EventLoop被析构的时候被释放,但是muduo代码里好像没有析构EventLoop的代码,而且EventLoopThread和EventLoopThreadPool持有EventLoop的方式是使用EventLoop*,也不是使用智能指针,不会自动清理,其实算是一个会造成内存泄漏的问题点吧。不过muduo框架也没有重复的去创建EventLoop的过程,而是在程序一开始运行就初始化好了,所以也不会造成别的问题吧,只要客户代码不反复的去创建TcpServer,那就不会出现问题,所以这是需要注意的一点!在flamingo里面,chatServer,你可以就把它当作是TcpServer,是使用单例模式创建的,所以就避免了这个问题。
后续做一个小测试,反复创建看看效果如何!

后面也没有特别需要解释说明的成员了,基本在注释里就写好了。

构造

我们来看看它的构造

EventLoop::EventLoop()
: looping_(false),
quit_(false),
eventHandling_(false),
callingPendingFunctors_(false),
threadId_(std::this_thread::get_id()),
timerQueue_(new TimerQueue(this)),
iteration_(0L),//LONG型
currentActiveChannel_(NULL)
{
    createWakeupfd();
    
#ifdef WIN32
    wakeupChannel_.reset(new Channel(this, wakeupFdSend_));
    poller_.reset(new SelectPoller(this));
    
#else
    wakeupChannel_.reset(new Channel(this, wakeupFd_));
    poller_.reset(new EPollPoller(this));//默认使用Epoll,用这个Eventloop初始化EpollPoller
#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;
}

接下来看看最主要的loop

void EventLoop::loop()
{
	//assert(!looping_);
	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();
		//return一个事件戳,并将收到了epoll事件,放入activeChannels_中,由后面的代码执行
		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;
		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());
}

while循环判断的标志时quit_,前一篇文章也说了,通过修改quit_为true来使得EventLoop结束。
我们主要关注while内的循环。

        activeChannels_.clear();
		//return一个事件戳,并将收到了epoll事件,放入activeChannels_中,由后面的代码执行
		pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);

首先是这个,它清空了activeChannels_,然后通过poll完成两件事情,第一返回时间戳,第二epoll将读取到事件的Channel,记录到activeChannels_。在flamingo中,EpollPoller类会维持一个。muduo也是如此。

typedef std::vector<struct epoll_event> EventList;

		int                 epollfd_;
		EventList           events_;//eventslist是一个事件数组,用vector存储,元素类型为epoll_event的结构体

事件会以struct epoll_event的形式被记录到events_中。

		eventHandling_ = true;//正在处理事件
		for (const auto& it : activeChannels_)//遍历vector
		{
			currentActiveChannel_ = it;
			currentActiveChannel_->handleEvent(pollReturnTime_);//处理事件
		}
		currentActiveChannel_ = nullptr;
		eventHandling_ = false;

这个过程就比较清晰了,将正在处理事件的标志置为true,然后遍历activeChannels_,调用Channel的handleEvent函数处理事件。
接下来是,dopendingFunctors。

void EventLoop::doPendingFunctors()
{
	std::vector<Functor> functors;
	callingPendingFunctors_ = true;

	{
		std::unique_lock<std::mutex> lock(mutex_);//加锁,防止这时候再push_back
		functors.swap(pendingFunctors_);//交换内容
	}
	
	for (size_t i = 0; i < functors.size(); ++i)
	{
		functors[i]();
	}
	callingPendingFunctors_ = false;
}

这里新开了一个std::vector functors,并做了交换,防止了因为函数再执行,阻塞别的线程push_back,这个设计还是不错的,具体可以参考一下这里。另外注意到了,这个大括号的使用,我简单试了一下,发现了以前不知道的新大陆。
muduo源码分析——EventLoop_第1张图片
相信能看出来了吧,在doPendingFunctors里,通过大括号结束了unique_lock的生命周期,从而释放了mutex_。vector的swap其实是指针的交换,vector的迭代器实现是指针,所以其实消耗非常低,这应该也是作者使用vector容器来存放的原因把。

写一下这个wakeup相关的东西

定义

 SOCKET                 wakeupFd_;          //EventLoop被析构的时候被释放掉。

初始化

bool EventLoop::createWakeupfd()
{

    //Linux上一个eventfd就够了,可以实现读写
    wakeupFd_ = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (wakeupFd_ < 0)
    {
        //让程序挂掉
        LOGF("Unable to create wakeup eventfd, EventLoop: 0x%x", this);
        return false;
    }
    return true;
}

关于这个evenfd,它是一个专门用来做通知用的文件描述符。用来做唤醒进程用。

queueInLoop

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

void EventLoop::queueInLoop(const Functor& cb)//将函数放入队列,等待执行,或者立即执行
{
	{
		std::unique_lock<std::mutex> lock(mutex_);
		pendingFunctors_.push_back(cb);
	}

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

第一个函数,其实就是判断线程是不是当前线程,如果是的话就立即执行,如果不是就压入pendingFunctors中。
第二个函数就是具体压入的步骤了。

你可能感兴趣的:(c++网络编程)