版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
EventLoopThread类剖析(事件循环线程类)
Muduo的并发模型为 one loop per thread + threadpool(计算线程池),为了方便今后使用,定义了EventLoopThread类,该类封装了I/O线程。
EventLoopThread创建了一个线程,在线程函数中创建了一个EvenLoop对象并调用EventLoop::loop()开启事件循环。
多个I/O线程可以用I/O线程池来管理,对应的类是EventLoopThreadPool,我们在后面进行剖析。
/***************************************************************
* Date:2019-08-01
* Description : EventLoopThread 专门创建一个线程用于执行Reactor的事
* 件循环,是辅助类,并非必须要使用的,完全由用户自己决定。
* 若不创建线程去执行事件循环,那么就在主线程中执行事件循环,由主线程
* 来监听所有的文件描述符。
*
* EventLoopThread这个类的作用就是开启一个线程,但是这个线程中有一个
* EventLoop,并且让这个EventLoop处于loop()状态,任何一个线程,只要创
* 建并运行了EventLoop,都称之为IO线程
****************************************************************/
EventLoopThread::EventLoopThread(const ThreadInitCallback& cb,
const string& name)
: loop_(NULL),
exiting_(false),
/* 设置线程函数,线程启动时会回调threadFunc */
thread_(std::bind(&EventLoopThread::threadFunc, this), name),
mutex_(),
cond_(mutex_),
callback_(cb)
{
}
EventLoopThread::~EventLoopThread()
{
exiting_ = true;
if (loop_ != NULL) // not 100% race-free, eg. threadFunc could be running callback_.
{
// still a tiny chance to call destructed object, if threadFunc exits just now.
// but when EventLoopThread destructs, usually programming is exiting anyway.
// 退出IO线程,让IO线程的loop循环退出,从而退出了IO线程
loop_->quit();
thread_.join();
}
}
/*****************************************************
* Date:2019-08-01
* Description : startLoop用来启动一个EventLoop
******************************************************/
EventLoop* EventLoopThread::startLoop()
{
assert(!thread_.started());
/* thread_.start()将开启一个线程执行threadFunc()函数 */
thread_.start();
EventLoop* loop = NULL;
{
MutexLockGuard lock(mutex_);
while (loop_ == NULL)
{
/* 需要等待EventLoop对象的创建,等待threadFunc的通知 */
cond_.wait();
}
loop = loop_;
}
return loop;
}
/**************************************************************
* Date:2019-08-01
* Description : threadFunc是线程函数,内部初始化一个EventLoop,
* 运行EventLoopThread内的回调callback_,并开启事件循环。
***************************************************************/
void EventLoopThread::threadFunc()
{
EventLoop loop;
if (callback_)
{
/* 将上面定义好的loop传入这个回调 */
callback_(&loop);
}
{
MutexLockGuard lock(mutex_);
loop_ = &loop;
/* 通知startLoop,EventLoop对象已经创建好,主线程可以将loop返回 */
cond_.notify();
}
/* 开启事件循环,直到EventLoopThread析构 */
loop.loop();
//assert(exiting_);
MutexLockGuard lock(mutex_);
loop_ = NULL;
}
EventLoopThreadPool剖析(事件驱动循环线程池类)
I/O线程池的功能是开启若干个IO线程,并让这些IO线程处于事件循环的状态。通过调用 EventLoopThread::startLoop()实现。
TcpServer中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个EventLoop::loop。
线程池的作用体现在:
- 用户启动TcpServer服务器时创建大量子线程,每个子线程创建一个EventLoop并开始执行EventLoop::loop。
- 主线程的线程池保存着创建的这些线程和EventLoop。
- 当Acceptor接收到客户端的连接请求后返回TcpServer,TcpServer创建TcpConnection用于管理tcp连接。
- TcpServer从事件驱动线程池中取出一个EventLoop,并将这个EventLoop传给TcpConnection的构造函数。
- TcpConnection创建用于管理套接字的Channel并注册到从线程池中取出的EventLoop的Poller中。
- 服务器继续监听。
EventLoopThreadPool既是一个线程池,又是一个EventLoop池,二者是等价的,一个EventLoop对应一个线程。
这种方式称为 one loop per thread,即 Reactor + 线程池
线程池的定义比较简单,唯一复杂的地方是由主线程创建子线程,子线程创建EventLoop并执行EventLoop::loop,主线程返回创建的EventLoop给线程池并保存起来。
使用方法如下:
EventLoop loop;
// 创建线程池
EventLoopThreadPool pool(&loop, "");
// 设置线程个数
poll.setThreadNum(100);
// 启动线程池
poll.start();
EventLoopThreadPool.h 如下 :
/****************************************************************
* Date:2019-08-01
* Description : EventLoopThreadPool是基于Muduo库中TcpServer类
* 专门设计的半同步半异步模式的线程池,该线程池中每个线程都有一个自己的
* EventLoop,而每一个EventLoop底层都是一个poll或epoll,它利用了自
* 身的poll或者epoll在没有事件的时候会阻塞住,在有事件发生时,epoll
* 监听到了会去处理事件。
***************************************************************/
class EventLoopThreadPool : noncopyable
{
public:
typedef std::function<void(EventLoop*)> ThreadInitCallback;
EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg);
~EventLoopThreadPool();
/* 设置线程池的线程数量 */
void setThreadNum(int numThreads) { numThreads_ = numThreads; }
/* 启动线程池 */
void start(const ThreadInitCallback& cb = ThreadInitCallback());
/* 获取下一个EventLoop对象,采用round-robin轮询调度算法 */
EventLoop* getNextLoop();
/* 通过hashCode获取EventLoop对象*/
/// with the same hash code, it will always return the same EventLoop
EventLoop* getLoopForHash(size_t hashCode);
/* 获取所有的EventLoop对象 */
std::vector<EventLoop*> getAllLoops();
/* 判断线程池是否启动 */
bool started() const
{ return started_; }
/* 获取线程池的名字 */
const string& name() const
{ return name_; }
private:
/**
* 主线程的事件驱动循环,TcpServer所在的事件驱动循环,
* 创建TcpServer传入的EventLoop
*/
EventLoop* baseLoop_;
string name_;
/* 线程池是否启动 */
bool started_;
/* 开启的线程数目 */
int numThreads_;
/* 负责轮询调度next值,新连接到来,所选择的EventLoop对象下标 */
int next_;
/* EventLoop线程池中所有的线程 */
std::vector<std::unique_ptr<EventLoopThread>> threads_;
/*
* 线程池中每个线程对应的事件驱动循环,从线程池取出线程实际上
* 返回的是事件驱动循环,每个事件驱动循环运行在一个线程中
*/
std::vector<EventLoop*> loops_;
};
下面是EventLoopThreadPool.cc的实现:
/* Ctor --> 初始化成员变量 */
EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop,
const string& nameArg)
: baseLoop_(baseLoop),
name_(nameArg),
started_(false),
numThreads_(0),
next_(0)
{
}
EventLoopThreadPool::~EventLoopThreadPool()
{
// Don't delete loop, it's stack variable
}
/************************************************************
* Date:2019-08-01
* Description:threadPool_->start()用于启动线程池,传入
*的参数是创建好所有线程后调用的回调函数,也是由用户提供
************************************************************/
void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
assert(!started_);
baseLoop_->assertInLoopThread();
/* 设置线程池为启动状态 */
started_ = true;
/* 开始根据所需的线程数量创建线程(事件驱动循环) */
for (int i = 0; i < numThreads_; ++i)
{
char buf[name_.size() + 32];
snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);
/* 构造EventLoopThread对象 */
EventLoopThread* t = new EventLoopThread(cb, buf);
/*
* threads_即为EventLoop线程池(vector,存储EventLoopThread
* 的对象的unique_ptr)
*/
threads_.push_back(std::unique_ptr<EventLoopThread>(t));
/*
* 启动每个EventLoop线程,startLoop会调用thread的start方法创建子线程,
* 返回一个指向该EventLoop线程的指针,在这里使用loops_保存每个线程所
* 对应的EventLoop,若需要监听的文件描述符小于线程总数,其余线程进入
* 睡眠状态,等待被唤醒。
*/
loops_.push_back(t->startLoop());
}
/*
* 如果线程数目为0,那么以主线程所对应的EventLoop为参执行回调,
* 去监听套接字
*/
if (numThreads_ == 0 && cb)
{
cb(baseLoop_);
}
}
/*****************************************************************
* Date:2019-08-01
* Description:getNextLoop()使用round-robin轮询算法
* 从EventLoop线程池中选择一个EventLoop
* 轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开
* 始,直到N(内部服务器个数),然后重新开始循环。轮询调度算法假设所有服务器
* 的处理性能都相同,不关心每台服务器的当前连接数和响应速度。当请求服务间隔
* 时间变化比较大时,轮询调度算法容易导致服务器间的负载不平衡。
****************************************************************/
EventLoop* EventLoopThreadPool::getNextLoop()
{
baseLoop_->assertInLoopThread();
assert(started_);
EventLoop* loop = baseLoop_;
if (!loops_.empty())
{
// round-robin
loop = loops_[next_];
++next_;
/* next_值大于等于loops_的长度了,从0重新开始 */
if (implicit_cast<size_t>(next_) >= loops_.size())
{
next_ = 0;
}
}
return loop;
}
/******************************************************
* Date:2019-08-01
* Description:通过哈希法从EventLoop线程池中选
* 择一个EventLoop。
*****************************************************/
EventLoop* EventLoopThreadPool::getLoopForHash(size_t hashCode)
{
baseLoop_->assertInLoopThread();
/* loops_为空直接返回主线程所对应的EventLoop */
EventLoop* loop = baseLoop_;
if (!loops_.empty())
{
/* hashCode模上loops_长度(即EventLoop对象个数) 得到下标*/
loop = loops_[hashCode % loops_.size()];
}
return loop;
}
/******************************************************
* Date:2019-08-01
* Description:获取所有的EventLoop。
*****************************************************/
std::vector<EventLoop*> EventLoopThreadPool::getAllLoops()
{
baseLoop_->assertInLoopThread();
assert(started_);
if (loops_.empty())
{
/* 若loops_为空,那么把主线程的EventLoop返回 */
return std::vector<EventLoop*>(1, baseLoop_);
}
else
{
return loops_;
}
}