EventLoopThreadPool是一个事件线程池,通过这个池来管理EventLoop,EventLoop本身绑定的就是线程
- EventLoopThreadPool.h
#pragma once
#include "noncopyable.h"
#include<functional>
#include<string>
#include<vector>
#include<memory>
class EventLoop;
class EventLoopThread;
class EventLoopThreadPool : noncopyable
{
public:
using ThreadInitCallback = std::function<void(EventLoop*)>;
EventLoopThreadPool(EventLoop* baseLoop, const std::string &nameArg);
~EventLoopThreadPool();
//设置底层线程的数量,TcpServer::setThreadNum底层调用的就是EventLoopThreadPool::setThreadNum
void setThreadNum(int numThreads) { numThreads_ = numThreads; }
//根据指定的线程数量在池里面创建numThread_个数的事件线程
void start(const ThreadInitCallback &cb = ThreadInitCallback());
//如果工作在多线程中,baseLoop_会默认以轮询的方式分配channel给subloop
EventLoop* getNextLoop();
//返回事件循环池所有的EventLoop,就是loops_
std::vector<EventLoop*> getAllLoops();
bool started()const { return started_; }
const std::string name()const { return name_; }
private:
EventLoop* baseLoop_; //我们使用muduo编写程序的时候,就会定义一个EventLoop变量,这个变量作为TcpServer构造函数的参数,用户创建的就叫做baseLoop
std::string name_; //线程池的名字
bool started_;
int numThreads_;
int next_; //轮询的下标
std::vector<std::unique_ptr<EventLoopThread>> threads_; //包含了创建的所有事件subloop的线程
std::vector<EventLoop*> loops_; // 包含了所有创建的subLoop的指针,这些EventLoop对象都是栈上的(见EventLoopThread::threadFunc)
};
成员变量baseLoop_
:我们使用muduo编写程序的时候,就会定义一个EventLoop变量,这个变量作为TcpServer构造函数的参数,用户创建的就叫做baseLoop
成员变量numThreads_
:表示subReactor的数量。我们使用muduo编写程序的时候,就会定义一个EventLoop变量,这个变量作为TcpServer构造函数的参数,这就是baseLoop。如果我们不使用setThreadNum指定Reactor模型线程数量,那么muduo默认使用单线程模型,这个线程既负责新用户连接,也负责已连接用户的读写事件
成员变量loops_
:包含了所有创建的subLoop的指针,这些EventLoop对象都是栈上的(见EventLoopThread::threadFunc),不需要我们手动释放。EventLoopThread::threadFunc函数是由线程执行的,EventLoop对象存在于线程栈上
- EventLoopThreadPool.cc
#include "EventLoopThreadPool.h"
#include "EventLoopThread.h"
#include<memory>
EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop, const std::string &nameArg)
:baseLoop_(baseLoop)
,name_(nameArg)
,started_(false)
,numThreads_(0)
,next_(0)
{}
//析构的时候不需要关注vector析构的时候里面存的EventLoop指针执行的外部资源是否需要单独delete,
//因为线程里面绑定的loop都是栈上的对象,不用担心需要手动delete它,它也不会释放掉,
//因为它下边的loop.loop()一直在循环,这个函数不会出右括号的,除非这个EventLoop底层的poller不工作了
//出右括号这个对象会自动析构的
EventLoopThreadPool::~EventLoopThreadPool()
{}
//根据指定的线程数量在池里面创建numThread_个数的事件线程
void EventLoopThreadPool::start(const ThreadInitCallback &cb)
{
started_ = true;
//用户通过setThreadNum设置了线程数就会进入循环
for(int i = 0; i < numThreads_; ++i)
{
char buf[name_.size() + 32];
snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i); //底层线程名字 = 线程池名字+循环下标
EventLoopThread *t = new EventLoopThread(cb, buf);
threads_.push_back(std::unique_ptr<EventLoopThread>(t)); // 用unique_ptr管理堆上的EventLoopThread对象,以免我们手动释放
loops_.push_back(t->startLoop()); //底层创建线程,绑定一个新的EventLoop,并返回该loop的地址
}
//整个服务端只有一个线程,运行着baseloop
if(numThreads_ == 0 && cb)
{
cb(baseLoop_);
}
}
//如果工作在多线程中,baseLoop_会默认以轮询的方式分配channel给subloop
EventLoop* EventLoopThreadPool::getNextLoop()
{
EventLoop* loop = baseLoop_;
if(!loops_.empty()) //通过轮询获取下一个处理事件的loop
{
loop = loops_[next_];
++next_;
if(next_ >= loops_.size())
{
next_ = 0;
}
}
return loop;
}
//返回事件循环池所有的EventLoop,就是loops_
std::vector<EventLoop*> EventLoopThreadPool::getAllLoops()
{
if(loops_.empty())
{
return std::vector<EventLoop*>(1,baseLoop_);
}
return loops_;
}
为什么muduo库采用固定数量的线程池,而不采用动态增长的线程池?
因为它只是一个网络库,只负责IO线程接收新连接和工作线程监听已连接客户端发生的读写事件,而不管是什么事件,耗不耗费服务端的资源。比较耗费资源的大任务应该由业务层处理,而不在网络库的职责范围内。网络库的EventLoopThreadPool只负责比较简单的监听事件,而不去处理具体的事件,所以我们开启的线程和CPU核数相同即可,可以让线程并行
在muduo网络库中,一个线程负责一个EventLoop,会监听很多fd。如果这个工作线程还要处理A用户到B用户文件传输请求的话,那么当前这个工作线程其他用户再有什么事件发生的话,这个工作线程就无法及时处理,所以像这种有耗时的IO操作还是需要有线程专门去做的