muduo网络库源码详解(3) 分析muduo的EventLoopThreadPool的多线程机制
主要参考了陈硕的书《Linux多线程服务端编程》,结合自己的逻辑摘了一些重要的的句子和函数、数据成员进行分析,
本文代码用C++11的多线程机制改写了muduo,没用muduo的base库,但原理都是一样的。
EchoServer的主函数
int main()
{
muduo::net::EventLoop loop; // 构造一个loop
muduo::net::InetAddress listenAddr(8888); // sockaddr_in的封装,服务地址未填写默认本地
EchoServer server(&loop, listenAddr); // TcpServer的初始化、构造函数里 手动绑定回调函数
server.start();
loop.loop();
}
上一篇讲到了TcpServer.start(),本文接着以TcpServer::start为入口分析多线程机制
// 开启服务器监听 loop.loop()
void TcpServer::start()
{
if (started_++ == 0) // 防止一个TcpServer对象被start多次
{
// 用户设置的回调函数threadInitCallback_,没设置为空
threadPool_->start(threadInitCallback_); // 启动threadPool_
loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())); // 在当前线程开启监听
}
}
文章目录
总览图 —— 细节都在图里了
红色的线标识的是 回调函数(用户设置的)的传递的时机
由TcpServer的构造时 构造的EventLoopThreadPool负责 muduo 多线程机制
图可放大
以下为详细的代码
EventLoopThreadPool::start
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); // 初始化一个EventLoopThread
threads_.push_back(std::unique_ptr<EventLoopThread>(t));
loops_.push_back(t->startLoop()); // 底层创建线程,绑定一个新的EventLoop,并返回该loop的地址
}
// 整个服务端只有一个线程,运行着baseloop
if (numThreads_ == 0 && cb)
{
cb(baseLoop_);
}
}
EventLoopThread::EventLoopThread
EventLoopThread::EventLoopThread(const ThreadInitCallback &cb,
const std::string &name)
: loop_(nullptr)
, exiting_(false)
, thread_(std::bind(&EventLoopThread::threadFunc, this), name) // 绑定threadFunc
, mutex_()
, cond_()
, callback_(cb)
{
}
Thread
void Thread::start() // 一个Thread对象。记录的就是一个新线程的详细信息
{
started_ = true;
sem_t sem;
sem_init(&sem, false, 0);
// 开启线程
thread_ = std::shared_ptr<std::thread>(new std::thread([&](){
tid_ = CurrentThread::tid();
sem_post(&sem);
// 开启一个新线程,专门执行该线程函数
func_(); //
}));
// 这里必须等待获取上面新创建的线程的tid值
sem_wait(&sem);
}
EventLoopThread::startLoop
EventLoop* EventLoopThread::startLoop()
{
thread_.start(); // 启动底层的新线程
EventLoop *loop = nullptr;
{
std::unique_lock<std::mutex> lock(mutex_);
while ( loop_ == nullptr )
{
cond_.wait(lock);
}
loop = loop_;
}
return loop;
}
void Thread::start
void Thread::start() // 一个Thread对象。记录的就是一个新线程的详细信息
{
started_ = true;
sem_t sem;
sem_init(&sem, false, 0);
// 开启线程
thread_ = std::shared_ptr<std::thread>(new std::thread([&](){
tid_ = CurrentThread::tid();
sem_post(&sem);
// 开启一个新线程,专门执行该线程函数
func_();
}));
// 这里必须等待获取上面新创建的线程的tid值
sem_wait(&sem);
}
EventLoopThread::threadFunc
// 下面这个方法,是在单独的新线程里面运行的
void EventLoopThread::threadFunc()
{
EventLoop loop; // 创建一个独立的eventloop,和上面的线程是一一对应的,one loop per thread
if (callback_) // 执行ThreadInitCallback
{
callback_(&loop);
}
{
std::unique_lock<std::mutex> lock(mutex_);
loop_ = &loop;
cond_.notify_one(); // 阻塞
}
loop.loop(); // EventLoop loop => Poller.poll
std::unique_lock<std::mutex> lock(mutex_);
loop_ = nullptr;
}
void EventLoop::loop()
// 开启事件循环
void EventLoop::loop()
{
looping_ = true;
quit_ = false;
LOG_INFO("EventLoop %p start looping \n", this);
while(!quit_)
{
activeChannels_.clear();
// 监听两类fd 一种是client的fd,一种wakeupfd
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
for (Channel *channel : activeChannels_)
{
// Poller监听哪些channel发生事件了,然后上报给EventLoop,通知channel处理相应的事件
channel->handleEvent(pollReturnTime_);
}
// 执行当前EventLoop事件循环需要处理的回调操作
/**
* IO线程 mainLoop accept fd《=channel subloop
* mainLoop 事先注册一个回调cb(需要subloop来执行) wakeup subloop后,执行下面的方法,执行之前mainloop注册的cb操作
*/
doPendingFunctors();
}
LOG_INFO("EventLoop %p stop looping. \n", this);
looping_ = false;
}