鸽了挺长时间,其实最后一部分早就写完了,但是最近实验有点多,一直在忙实验。
这次新增了很多东西,首先是Buffer类,用来处理接收或者要发送的信息,不再像以前一样收到一次发一次,而是把收到或者要发送的消息汇总起来,等到输入结束的时候在一起发送,其实用起来跟之前也什么区别,但是封装之后使用更方便一点。Buffer类管理一个string字符串,比较简单,不再赘述。
还增加了线程池,原先服务器所有的事件都是由Eventloop管理的,单线程处理,加入同时处理1000个1s的事件,则会阻塞相当长时间。在前面提到的Reactor模式中讲过,Reactor应该只负责分发事件,由处理事件线程来处理时间。这里添加了一个简单的线程池,线程的数量取决于CPU,我在Linux虚拟机上看是只有八个,可能是虚拟机核数分少了,事件活跃时将时间添加到任务队列,然后取出由各线程完成。
需要注意的时多线程操作需要配合互斥锁来使用,避免同时对某一资源读写时造成不可预见的后果,同时采用std::condition_variable来通知对应线程处理避免CPU轮询任务队列造成资源浪费。代码如下:
template<class F, class... Args>
auto ThreadPool::add(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
using returnType = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<returnType()>> (
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<returnType> res = task->get_future();
{
std::unique_lock<std::mutex> lock(tasksMtx);
if (stop) {
throw std::runtime_error("enqueue on stopped Thread pool");
}
tasks.emplace([task](){ (*task)(); });
}
cv.notify_one();
return res;
}
看起来相当的恐怖,采用了模板和大量的c++新特性,returnType为返回值类型,task是一个指向bind绑定的函数对象的返回值类型的指针,packaged_task用于封装bind的绑定对象,并能将其返回值传递给future对象,供后面res的get_future()调用。然后是大括号内的互斥锁作用域,保证该对象在同一时刻只有一个线程访问,也就是将其添加到tasks任务队列中,之所以再开一个作用域是因为当退出作用域后锁会自动释放。
最后调用notify_one()通知线程池获取任务在处理。
然后将服务器由单Reacor改为主从Reactor多线程模式,
该模式只有一个mainReactor,有很多个subReactor。
服务器管理一个线程池,每一个subReactor由一个线程来负责Connection上的事件循环,事件执行也在这个线程中完成。
mainReactor只负责Acceptor建立新连接,然后将这个连接分配给一个subReactor。
在构造服务器时
//Acceptor由且只由mainReactor负责
Server::Server(pEventLoop _loop) : mainReactor(_loop), acceptor(new Acceptor(mainReactor)), thPool(new ThreadPool()) {
std::function<void(pSocket)> cb = std::bind(&Server::newConnection, this, std::placeholders::_1);
acceptor->setNewConnectionCB(cb);
int size = std::thread::hardware_concurrency();//线程数量,也是subReactor数量
for (int i = 0; i < size; i++) {
subReactors.emplace_back(pEventLoop(new EventLoop())); //每一个线程是一个EventLoop
}
for (int i = 0; i < size; i++) {
std::function<void()> subLoop = std::bind(&EventLoop::loop, subReactors[i]);
thPool->add(std::move(subLoop)); //开启所有线程的事件循环
}
}
在新连接到来时,我们需要将这个连接的socket描述符添加到一个subReactor中:
int random = sock->getFd() % subReactors.size(); //调度策略:全随机
Connection *conn = new Connection(subReactors[random], sock); //分配给一个subReactor