现在开始写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()可以正确执行,而不用为了这种情况另外写一个函数,算是降低程序的复杂度,或者说增加了复用性吧。这里可以看出,主线程和工作线程是严格分开管理的。
关于其他的成员,意义比较明显,就不做过多的解释了。
我把构造和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这个类。
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的函数吧
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,那么来看看这个函数
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()唤醒进程。
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的介绍这里大家可以看看。