muduo网络库剖析——线程Thread类
- 前情
- 从muduo到my_muduo
- 概要
- 框架与细节
- 成员
- 函数
- 使用方法
- 源码
- 结尾
前情
从muduo到my_muduo
作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精华进行简要实现,这要求我们足够了解muduo库。
做项目 = 模仿 + 修改,不要担心自己学了也不会写怎么办,重要的是积累,学到了这些方法,如果下次在遇到通用需求的时候你能够回想起之前的解决方法就够了。送上一段话!
概要
对于一个one loop one thread,我们有EventLoopThread类,对于多个one loop one thread,我们需要用一个EventLoopThreadPool事先去创建好这个pool。像线程池那样,这样的好处就是存取的时候比较方便,开销更小。
框架与细节
成员
在几个loop,有一个mainLoop和其他的subLoop。由mainloop来创建其他的subloop。因此,我们在EventLoopThreadPool中只留下了baseLoop_,也就是mainLoop。以及它的名字,thread的数量以及对应数量下的EventLoopThread表。和对应数量下的EventLoop表。另外,对于多个EventLoopThread,是采用轮询的方式,因此还需要一个next_记录mainLoop后一个loop是哪个。
函数
首先是EventLoopThreadPool的构造函数和析构函数。
对于构造函数,传入参数是mainloop指针,以及对应线程的名字。此时因为没有初始化mainloop,所以传入的线程数量参数也是0。next也初始化为0。
对于EventLoopThreadPool中的start方法,目标是创建EventLoopThread表中的所有EventLoopThread,并启动所有的loop,相应的EventLoopThread和loop也需要存入到对应的数据结构中,如果没有subLoop,那只能启动mainLoop了。但是在正常情况下应该是启动subLoop,因为mainloop除了处理自己的循环事务之外,对于新的连接它还需要监听,对于新的subloop他还需要创建,因此想要负载均衡,最好是调用subloop来分摊mainloop的压力。而numthread_一开始是0,所以一开始是启动mainloop,而后在mainloop创建新的subloop时,对应的numthread_应该也会增加。因此对于私有的numthread_成员,我们要提供一个公有函数的接口
对于在EventLoopThreadPool的数据创建好之后,我们需要提供给用户轮询整个EventLoopThreadPool的接口,即getNextLoop函数。在这个函数中,我们需要从mainloop开始轮询。所以先使用一个EventLoop指针,指向mainloop.通过我们的EventLoop表,进行轮询,在轮询的时候注意next指针的变化。并需要记住轮询是尾部和头部的衔接,最后返回next loop。这里有个比较关键的点是为什么我们不用EventLoopThread来遍历,而使用EventLoop来遍历。因为EventLoop是我们处理事件循环的功能类,而EventLoopThread主要是事件循环和线程相对应的衔接类,因此基于功能来讲,我们在EventLoopThreadPool需要留下这样一个功能类表,也是我们返回EventLoop的原因。
最后,还提供了一个获取所有EventLoop的函数接口,有利于我们debug。
使用方法
源码
//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();
void setThreadNum(int numThreads) { numThreads_ = numThreads; }
void start(const ThreadInitCallback &cb = ThreadInitCallback());
// 如果工作在多线程中,baseLoop_默认以轮询的方式分配channel给subloop
EventLoop* getNextLoop();
std::vector<EventLoop*> getAllLoops();
bool started() const { return started_; }
const std::string name() const { return name_; }
private:
EventLoop *baseLoop_; // EventLoop loop;
std::string name_;
bool started_;
int numThreads_;
int next_;
std::vector<std::unique_ptr<EventLoopThread>> threads_;
std::vector<EventLoop*> loops_;
};
//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) {}
EventLoopThreadPool::~EventLoopThreadPool() {}
void EventLoopThreadPool::start(const ThreadInitCallback &cb) {
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 *t = new EventLoopThread(cb, buf);
threads_.push_back(std::unique_ptr<EventLoopThread>(t));
loops_.push_back(t->startLoop()); // 底层创建线程,绑定一个新的EventLoop,并返回该loop的地址
}
// 整个服务端只有一个线程,运行着baseloop
if (numThreads_ == 0 && cb) {
cb(baseLoop_);
}
}
// 如果工作在多线程中,baseLoop_默认以轮询的方式分配channel给subloop
EventLoop* EventLoopThreadPool::getNextLoop() {
EventLoop *loop = baseLoop_;
// 通过轮询获取下一个处理事件的loop
if (!loops_.empty()) {
loop = loops_[next_];
++next_;
if (next_ >= loops_.size()) {
next_ = 0;
}
}
return loop;
}
std::vector<EventLoop*> EventLoopThreadPool::getAllLoops() {
if (loops_.empty()) {
return std::vector<EventLoop*>(1, baseLoop_);
}
else {
loops_;
}
}
结尾
以上就是事件循环线程池EventLoopThreadPool类的相关介绍,以及我在进行项目重写的时候遇到的一些问题,和我自己的一些心得体会。发现写博客真的会记录好多你的成长,而且对于一个好的项目,写博客也是证明你确实有过深度思考,并且在之后面试或者工作时遇到同样的问题能够进行复盘的一种有效的手段。所以,希望uu们也可以像我一样,养成写博客的习惯,逐渐脱离菜鸡队列,向大佬前进!!!加油!!!
也希望我能够完成muduo网络库项目的深度学习与重写,并在功能上能够拓展。也希望在完成这个博客系列之后,能够引导想要学习muduo网络库源码的人,更好地探索这篇美丽繁华的土壤。致敬chenshuo大神!!!
鉴于博主只是一名平平无奇的大三学生,没什么项目经验,所以可能很多东西有所疏漏,如果有大神发现了,还劳烦您在评论区留言,我会努力尝试解决问题!