《Muduo网络库:实现one loop per thread设计模式》

自此,我们掌握了EventLoop事件循环类,事件到来之后可以跨线程任务调度从而避免了直接操作EventLoop非线程安全状态造成的线程安全问题。

EventLoop是运行在一个线程中的,并且一个线程运行一个EventLoop,one loop per thread是如何实现的呢?

实现Thread线程类

专门处理一个线程。

Thread.h

cpp 复制代码
#pragma once

#include "noncopyable.h"

#include <functional>
#include <thread>
#include <memory>
#include <unistd.h>
#include <string>
#include <atomic>

class Thread : noncopyable
{
public:
    using ThreadFunc = std::function<void()>;

    explicit Thread(ThreadFunc, const std::string &name = std::string());
    ~Thread();

    void start();
    void join();

    bool started() const { return started_; }
    pid_t tid() const { return tid_; }
    const std::string &name() const { return name_; }

    static int numCreated() { return numCreated_; }

private:
    // 生成默认线程名
    void setDefaultName();

    bool started_;
    bool joined_;
    std::shared_ptr<std::thread> thread_;
    pid_t tid_;
    ThreadFunc func_;
    std::string name_;
    static std::atomic_int numCreated_; // 静态原子变量确保线程数量和线程名唯一
};

Thread.cc

cpp 复制代码
#include "Thread.h"
#include "CurrentThread.h"
#include <semaphore.h>

Thread::Thread(ThreadFunc func, const std::string &name)
    : started_(false), joined_(false), tid_(0), func_(std::move(func)), name_(name)
{
    setDefaultName();
}

Thread::~Thread()
{
    if (started_ && !joined_)
    {
        thread_->detach(); // 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值
        tid_=CurrentThread::tid();
        // 发送信号,告知主线程"tid以获取"
        sem_post(&sem);  
        // 开启的新线程,专门执行该线程函数
        func_(); }));

    // 这里主线程必须等待获取上面创建的新线程的tid值
    sem_wait(&sem);
}

void Thread::join()
{
    joined_ = true;
    thread_->join();
}

// 生成默认线程名"Thread1""Thread2"
void Thread::setDefaultName()
{
    int num = ++numCreated_;
    if (name_.empty())
    {
        char buf[32] = {0};
        snprintf(buf, sizeof buf, "Thread%d", num);
        name_ = buf;
    }
}

使用std::shared_ptr<std::thread>管理线程对象,提供更灵活的生命周期管理。

来分析一下Thread类实现的核心方法,start()方法:

1、使用信号量同步线程创建

2、new创建新线程,在Lambda表达式中:

  • 获取新线程id值并存储
  • 发送信号量通知主线程已获取id
  • 执行用户提供的线程函数

3、使用信号量主动等待,确保start()方法返回前主线程已获取到新线程的id

实现EventLoopThread类

Thread类实现完成,如何在一个线程中运行一个EventLoop?也就是怎么结合EventLoop和Thread实现one loop per thread模型?

Muduo实现了EventLoopThread事件循环线程类,它是一个将线程与事件循环EventLoop绑定的一个类,核心实现one loop per thread,每个线程独立运行一个EventLoop。

EventLoopThread.h

cpp 复制代码
#pragma once

#include "noncopyable.h"
#include "Thread.h"

#include <functional>
#include <mutex>
#include <condition_variable>
#include <string>

class EventLoop;

class EventLoopThread : noncopyable
{
public:
    using ThreadInitCallback = std::function<void(EventLoop *)>;

    EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback(), const std::string &name = std::string());
    ~EventLoopThread();

    EventLoop *startLoop();

private:
    void threadFunc();

    EventLoop *loop_;  // 将在新线程中指向创建的EventLoop对象
    bool exiting_;  // 标记是否退出
    Thread thread_;  // 创建底层线程对象,绑定线程的入口函数为当前类的成员函数threadFunc,并指定线程名称
    std::mutex mutex_;
    std::condition_variable cond_;
    ThreadInitCallback callback_;  // 保存用户传入的初始化回调,用于在EventLoop启动前做额外配置
};

EventLoopThread.cc

cpp 复制代码
#include "EventLoopThread.h"
#include "EventLoop.h"

EventLoopThread::EventLoopThread(const ThreadInitCallback &cb, const std::string &name)
    : loop_(nullptr), exiting_(false), thread_(std::bind(&EventLoopThread::threadFunc, this), name), mutex_(), cond_(), callback_(cb)
{
}

EventLoopThread::~EventLoopThread()
{
    exiting_ = true;
    if (loop_ != nullptr)
    {
        loop_->quit();  // EventLoop退出事件循环
        thread_.join(); // 主线程主动等待新线程结束,回收线程资源
    }
}

EventLoop *EventLoopThread::startLoop()
{
    thread_.start(); // 启动(创建)底层的新线程,触发threadFunc()执行

    EventLoop *loop = nullptr;
    {
        std::unique_lock<std::mutex> lock(mutex_);
        // 当loop_为空时,必须主动等待新线程中loop_被初始化
        while (loop_ == nullptr)
        {
            cond_.wait(lock); // 主动等待,释放锁并阻塞,直到被唤醒
        }
        loop = loop_; // 获取初始化完成的EventLoop指针
    }
    return loop;
}

// 创建的新线程执行的线程函数
void EventLoopThread::threadFunc()
{
    // 在新线程栈上创建EventLoop对象(与该线程绑定),one loop per thread
    EventLoop loop;
    if (callback_)
    {
        callback_(&loop); // 执行用户初始化回调(设置线程名称)
    }

    {
        std::unique_lock<std::mutex> lock(mutex_);
        loop_ = &loop;      // 将loop_指向新创建的EventLoop
        cond_.notify_one(); // 通知主线程,loop_已初始化完成
    }
    loop.loop(); // 启动事件循环,执行EventLoop的loop()=> 开启Poller.poll()(阻塞,直到loop_.quit()被调用)

    // 事件循环退出后清理
    std::unique_lock<std::mutex> lock(mutex_);
    loop_ = nullptr;
}

这种设计就是高性能网络库Muduo的典型实现,通过"one loop per thread"充分利用多核CPU,同时简化线程间同步。

来分析一下其核心方法,startLoop()方法:

启动线程并等待EventLoop初始化完成,返回其指针。

1、调用thread_.start()启动底层线程,此时新线程会执行threadFunc()函数

2、主线程通过条件变量等待新线程中的EventLoop完全初始化:

  • 加锁后检查loop_是否为空(初始状态)
  • 若未初始化,则在条件变量上等待并释放锁阻塞,直到新线程通知loop_已赋值

3、最终返回初始化完成的EventLoop*,确保外部拿到的指针一定有效。

再来分析一下线程入口函数,threadFunc()方法:

新线程的执行逻辑,负责创建EventLoop对象并运行事件循环。

1、创建EventLoop:在新线程的栈上创建EventLoop对象,生命周期与线程绑定,线程结束则对象销毁。

2、执行初始化回调:若用户传入callback_,则在事件循环启动前执行。

3、通知主线程:

  • 加锁后将loop_指向新创建的EventLoop对象
  • 调用cond_.notify_one()唤醒主线程的cond_.wait(lock),告知loop_已就绪

4、启动事件循环:调用loop.loop()进入事件循环(处理IO事件,直到loop_->quie()被调用)。

5、清理工作:事件循环退出后,将loop_重置为nullptr(线程即将结束)。

整体EventLoopThread流程

负责启动新线程,并在其线程栈上创建EventLoop对象与其绑定。

1、创建对象:EventLoop构造时,绑定线程入口函数threadFunc(),但线程未启动。

2、启动流程:

  • 外部调用startLoop(),触发thread_.start()启动新线程,新线程执行threadFunc()
  • threadFunc()中创建EventLoop并通过条件变量通知主线程,主线程从startLoop()返回EventLoop*(与新线程绑定的EventLoop对象指针)

3、运行阶段:EventLoop在新线程中通过loop.loop()持续运行,处理事件。

3、销毁流程:

  • 析构EventLoopThread时,设置exiting_=true,调用loop_.quit()退出事件循环
  • 新线程执行loop_=nullptr后结束,主线程通过thread_。join()等待线程回收

核心设计亮点

**线程与事件循环绑定:**EventLoop对象在新线程栈上创建,确保其生命周期与线程一致,避免跨线程访问问题。

**同步机制:**通过互斥锁+条件变量解决主线程与新线程的竞态条件,确保startLoop()返回的EventLoop*一定有效,保证了线程安全

**可扩展性:**支持线程初始化回调ThreadInitCallback(),允许用户在事件循环启动前自定义配置(如设置线程名称、注册初始事件等)

实现EventLoopThreadPool线程池类

在多线程工作环境下肯定存在多个EventLoopThread对象,如何管理多个EventLoopThread对象?

Muduo实现了EventLoopThreadPool事件循环线程池类,用于管理多个EventLoopThread对象,实现了one loop per thread模式的线程池版本。可以根据配置创建多个事件循环线程EventLoopThread,并提供EventLoop的分配机制。

EventLoopThreadPool.h

cpp 复制代码
#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());

    // 获取下一个处理事件的EventLoop
    EventLoop *getNextLoop();

    // 获取所有EventLoop
    std::vector<EventLoop *> getAllLoops();

    bool started() const { return started_; }

    const std::string &name() const { return name_; }

private:
    EventLoop *baseLoop_; // 用户自己的(主线程的)EventLoop, EventLoop loop,不参与线程池管理
    std::string name_;    // 线程池名字
    bool started_;        // 标志线程池是否启动
    int numThreads_;      // 线程池大小,要创建的事件循环线程数量
    int next_;            // 轮询算法的索引,用于分配下一个事件循环
    std::vector<std::unique_ptr<EventLoopThread>> threads_;
    std::vector<EventLoop *> loops_; // 存储所有子线程的事件循环的指针
};

EventLoopThreadPool.cc

cpp 复制代码
#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()
{
    /**
     * 不需要显示释放资源
     * 1. threads_存储的是unique_ptr管理的EventLoopThread,会自动释放EventLoopThread对象
     * 2. EventLoop对象在EventLoopThread的线程栈上创建的,线程结束时会自动销毁
     */
}

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)); // 将每一个EventLoopThread存入threads_

        // 启动每一个EventLoopThread并将返回的EventLoop*存入loops_
        loops_.push_back(t->startLoop()); // 底层启动创建线程,绑定一个新的EventLoop,并返回该loop的地址
    }

    // 如果线程数为0表示整个服务端只有一个主线程线程,直接在主线程的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;
}

std::vector<EventLoop *> EventLoopThreadPool::getAllLoops()
{
    if (loops_.empty())
    {
        return std::vector<EventLoop *>(1, baseLoop_);
    }
    else
    {
        return loops_;
    }
}

整体工作流程

1、初始化线程池,设置线程数量

2、调用start()方法启动线程池,创建指定数量的EventLoopThread事件循环线程对象

3、调用每一个EventLoopThread对象的startLoop()方法,创建一个线程和其对应的EventLoop

4、通过getNext Loop()方法以轮询(按顺序)的方式获取事件循环,分配任务

5、内部通过loops_容器管理所有子线程的事件循环,start Loop()方法会返回子线程绑定的EventLoop的指针

核心设计亮点

**分层设计:**线程池不直接管理线程,而是通过EventLoopThread间接管理,职责清晰

**灵活配置:**可通过setThreadNum()动态设置线程数量

**负载均衡:**通过轮询算法为每个事件循环分配任务,简单高效

**线程安全:**通过EventLoopThread内部的同步机制保证线程安全

**兼容性:**支持单线程模式(线程数为0时使用主线程的事件循环)

以上,就完成了EventLoopThreadPool事件循环线程池类,这种设计,通过多线程事件循环充分利用多核CPU,提高了并发处理能力。

相关推荐
linksinke4 小时前
html案例:制作一个图片水印生成器,防止复印件被滥用
开发语言·前端·程序人生·html
^_^ 纵歌4 小时前
rust主要用于哪些领域
开发语言·后端·rust
_OP_CHEN4 小时前
C++基础:(十三)list类的模拟实现
开发语言·c++·反向迭代器·stl·list·list模拟实现·vector和list对比
froginwe114 小时前
R Excel 文件:高效数据处理与可视化分析利器
开发语言
练习时长一年4 小时前
@Scope失效问题
java·开发语言
淘晶驰AK4 小时前
主流的 MCU 开发语言为什么是 C 而不是 C++?
c语言·开发语言·单片机
胖咕噜的稞达鸭4 小时前
算法入门:专题二---滑动窗口(长度最小的子数组)更新中
c语言·数据结构·c++·算法·推荐算法
敲上瘾6 小时前
Linux系统C++开发环境搭建工具(二)—— etcd 使用指南
linux·c++·etcd
流星白龙6 小时前
【Qt】1.安装QT
开发语言·qt