muduo源码分析——EventLoopThreadPool和EventLoopThread

现在开始写EventLoop相关的类,我决定先写EventLoopThreadPool。

因为从拥有的关系上来看,TcpServer拥有一个EventLoopThreadPool,然后EventLoopThreadPool拥有若干个EventLoopThread(由TcpServer初始化时决定)。至于是不是直接拥有EventLoop,我觉得吧,可以说拥有,也不好说,因为它并没有去操作这些EventLoop,只能说是一个管理器。实现池这么一个东西。

成员

	private:

		EventLoop*                                      baseLoop_;//创建一个指针,方便后续好操作吧
		std::string                                     name_;
		bool                                            started_;//标志
		int                                             numThreads_;//线程数
		int                                             next_;//负载均衡的标记
		std::vector<std::shared_ptr<EventLoopThread> >  threads_;//池内的线程
		std::vector<EventLoop*>                         loops_;//具体的EventLoop

baseLoop_我认为是用来锁定主EventLoop的,因为可能需要随时拿到主Loop吧,但是后来在阅读EventLoop* EventLoopThreadPool::getNextLoop()时发现

EventLoop* EventLoopThreadPool::getNextLoop()
{
	baseLoop_->assertInLoopThread();
    //assert(started_);
    if (!started_)
        return NULL;
	
	EventLoop* loop = baseLoop_;

	if (!loops_.empty())//如果线程池不为空,其实就是防止,只有一个主线程,因为在TcpServer初始化的时候,如果传入0,则没有其他的工作线程了
	{
		// round-robin
		loop = loops_[next_];//取标记的下一个线程,目的是负载均衡
		++next_;
		if (implicit_cast<size_t>(next_) >= loops_.size())//超出时回归0
		{
			next_ = 0;
		}
	}
	return loop;
}

其实还有一个作用,就是当线程只有一个主EventLoop,而没有初始化其他工作线程的时候,让这个getNextLoop()可以正确执行,而不用为了这种情况另外写一个函数,算是降低程序的复杂度,或者说增加了复用性吧。这里可以看出,主线程和工作线程是严格分开管理的。

关于其他的成员,意义比较明显,就不做过多的解释了。

EventLoopThreadPool初始化及start

我把构造和init及start放在一起吧,其实内容不多也不复杂。

EventLoopThreadPool::EventLoopThreadPool()
: baseLoop_(NULL),
started_(false),
numThreads_(0),
next_(0)
{
}

EventLoopThreadPool::~EventLoopThreadPool()
{
	// Don't delete loop, it's stack variable
}

void EventLoopThreadPool::Init(EventLoop* baseLoop, int numThreads)
{
	numThreads_ = numThreads;
	baseLoop_ = baseLoop;
}

void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
    //assert(baseLoop_);
    if (baseLoop_ == NULL)
        return;
    
    assert(!started_);
    if (started_)//防止重复start
        return;  
	
	baseLoop_->assertInLoopThread();

	started_ = true;

	for (int i = 0; i < numThreads_; ++i)
	{
		char buf[128];
		snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);//将后面的内容,按格式化拷贝给buf

		std::shared_ptr<EventLoopThread> t(new EventLoopThread(cb, buf));
		//EventLoopThread* t = new EventLoopThread(cb, buf);
		threads_.push_back(t);//压入线程到线程池里
		loops_.push_back(t->startLoop());//里面返回指向一个已经开始loop的EventLoop的指针,并压入成员loops_中
	}
	if (numThreads_ == 0 && cb)
	{
		cb(baseLoop_);//ThreadInitCallback,这里不是特别理解
	}
}

从构造来看,baseLoop_默认初始化为NULL,但是实际上TcpServer会传入一个EventLoop给他,start也会默认是false,需要被start,next被初始化为了0,因为第一个被加入任务的当然是第0个线程嘛,其实我觉得这个初始化为几都可以,因为都可以实现负载均衡,但是!为了防止,初始化成了一个比实际线程数-1还要大的数,所以初始化了0。然后关于后面的我觉得没什么需要说明的,因为逻辑也不难。

EventLoopThread

接下来我们看看EventLoopThread这个类。

成员

	private:
		void threadFunc();

		EventLoop*                   loop_;//管理的EventLoop
		bool                         exiting_;//是否退出了
		std::shared_ptr<std::thread> thread_;//对应的实际的线程
		std::mutex                   mutex_;//保证线程安全
		std::condition_variable      cond_;//cond_.wait()函数可以解锁后在沉睡。条件变量
		ThreadInitCallback           callback_;
	};

先简单说一说exiting_这个变量,其实就是标记这个Thread有没有退出,但是在外部一直没有找到有引用这个变量的地方,所以其实可能没什么具体的用处吧。只是纯粹作为一个标记而已。至于其他的成员,我注释了,接下来我们来看看EventLoopThread的函数吧

startLoop

EventLoop* EventLoopThread::startLoop()
{
	//assert(!thread_.started());
	//thread_.start();

	thread_.reset(new std::thread(std::bind(&EventLoopThread::threadFunc, this)));//开新线程了

	{
		std::unique_lock<std::mutex> lock(mutex_);
		while (loop_ == NULL)
		{
			cond_.wait(lock);//让线程休眠
		}
	}

	return loop_;
}

简单介绍一下std::condition_variable,这里给大家一个链接,介绍的挺不错的c++条件变量
cond_.wait()可以让一个线程休眠,然后使用notify_one()或者notify_all()唤醒,另外一点不能使用std::lock_guard,而要用std::unique_lock,具体原因,在上面一个链接中解释的很清楚了,大概就是wait是先调用unlock()然后再睡眠,std::lock_guard没有这个实现哦。

回到这个函数,其实也比较简单,就是新开一条线程,执行的是EventLoopThread::threadFunc,那么来看看这个函数

threadFunc

void EventLoopThread::threadFunc()
{
	EventLoop loop;

	if (callback_)
	{
		callback_(&loop);
	}

	{
		//一个一个的线程创建
        std::unique_lock<std::mutex> lock(mutex_);//条件变量必须和std::unique_lock配合,因为实现了unlock和lock机制
		loop_ = &loop;
		cond_.notify_all();
	}

	loop.loop();//开始循环
	//assert(exiting_);
	loop_ = NULL;
}

简单说一下这里的锁的逻辑,简单来说,当线程启动完毕后,loop_就!=NULL了,而最后又要把loop_=NULL,目的就是再要去阻塞前面的startLoop函数,

老实说这个callback,我一直都没有找到具体的实现,它是EventLoopThreadPool注册的,但是在被TcpServer调用start时传入的,但是在TcpServer调用start的时候就没有传入过参数,所以有点莫名奇妙,这个问题有待解决。我认为是传入了一个函数指针,但是这个指针并没有具体指向一个实现的函数,而是空的。晚点我去打个断点,看看这里会不会被执行

	if (callback_)
	{
		callback_(&loop);
	}

再好好研究一下。
重新回到前面的内容,我们可以看到startLoop返回了loop_,所以我们需要等loop_被赋值后,才能去返回,否则返回的是NULL那就有问题了,所以你会看到在loop_ = &loop;执行完后,才会notify_all()唤醒进程。

stopLoop

void EventLoopThread::stopLoop()
{
    if (loop_ != NULL)
        loop_->quit();
 //具体的eventLoop的quit_被设置为true,具体的代码就不探讨了,在eventLoop中再讨论

    thread_->join();//等待this标志的进程结束,然后会清理掉内存。
}

设置具体的标志,然后join清理掉,当EventLoop的quit_标志为true的时候,EventLoop的while主循环会被退出,具体可以看一下EventLoop的代码,一看就知道了 ,但这里不展开,因为现在还不讨论EventLoop。然后等EventLoop里的Loop函数执行完,就可以用join()清理内存,并释放掉了,这就是退出的方法。我找了个join的介绍这里大家可以看看。

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