【sylar】框架篇-Chapter7-IO 协程调度模块

站在巨人的肩膀上

C++高性能分布式服务器框架

从零开始重写sylar C++高性能分布式服务器框架

概述

  • 基于 epoll 的IO协程调度器。
  • 支持读写事件。
  • IO协程调度器还解决了上一章协程调度器在 idle 状态下忙等待导致 CPU 占用率高的问题。IO协程调度器使用一对管道 fd 来 tickle 调度协程,当调度器空闲时,idle 协程通过 epoll_wait 阻塞在管道的读描述符上,等管道的可读事件。添加新任务时,tickle 方法写管道,idle 协程检测到管道可读后退出,调度器执行调度。
  • IO协程调度支持为描述符注册可读和可写事件的回调函数,当描述符可读或可写时,执行对应的回调函数(包装在 FdContext::EventContext 里面)。

IOManager

  • IO协程调度器类。
  • 继承自协程调度器。
  • 重载了 Scheduler 的 tickle 和 idle 方法。

其他说明

  • 对每个 fd,sylar 支持两类事件,一类是可读事件(对应 EPOLLIN),一类是可写事件(对应EPOLLOUT),sylar的事件枚举值直接继承自 epoll。
  • 当然 epoll 本身除了支持了 EPOLLIN 和 EPOLLOUT 两类事件外,还支持其他事件,比如 EPOLLRDHUP, EPOLLERR, EPOLLHUP 等,对于这些事件,sylar 的做法是将其进行归类,分别对应到 EPOLLIN 和 EPOLLOUT 中,也就是所有的事件都可以表示为可读或可写事件,甚至有的事件还可以同时表示可读及可写事件,比如 EPOLLERR 事件发生时,fd 将同时触发可读和可写事件。
  • 在执行 epoll_ctl 时通过 epoll_event 的私有数据指针 data.ptr 来保存 FdContext 结构体信息,其中 FdContext 结构体信息包括描述符 fd、所含事件 events、回调函数 EventContext。
  • IO协程调度器在 idle 时会 epoll_wait 所有注册的fd,如果有 fd 满足条件,epoll_wait 返回,从私有数据中拿到 fd 的上下文信息(也就是 data.ptr 里面存放的 FdContext),并且执行其中的回调函数(实际是 idle 协程只负责收集所有已触发的 fd 的回调函数并将其加入调度器的任务队列,真正的执行时机是 idle 协程退出后,调度器在下一轮调度时执行)。
  • epoll 是线程安全的,即使调度器有多个调度线程,它们也可以共用同一个 epoll 实例,而不用担心互斥。由于空闲时所有线程都阻塞的 epoll_wait 上,所以也不用担心 CPU 占用问题。
  • addEvent 是一次性的,比如说,注册了一个读事件,当 fd 可读时会触发该事件,但触发完之后,这次注册的事件就失效了,后面 fd 再次可读时,并不会继续执行该事件回调,如果要持续触发事件的回调,那每次事件处理完都要手动再 addEvent。这样在应对 fd 的 WRITE 事件时会比较好处理,因为 fd 可写是常态,如果注册一次就一直有效,那么可写事件就必须在执行完之后就删除掉。

部分相关代码

/**
 * @filename    iomanager.h
 * @brief   IO协程调度模块
 * @author  L-ge
 * @version 0.1
 * @modify  2022-06-30
 */
#ifndef __SYLAR_IOMANAGER_H__
#define __SYLAR_IOMANAGER_H__

#include "scheduler.h"
#include "timer.h"

namespace sylar
{

/**
 * @brief   基于 epoll 的 IO 协程调度器
 */
class IOManager : public Scheduler, public TimerManager
{
public:
    typedef std::shared_ptr<IOManager> ptr;
    typedef RWMutex RWMutexType;

    /**
     * @brief  IO事件,直接继承自 epoll 对事件的定义
     *         只关心读写事件,其他事件也会归类到这两类事件
     */
    enum Event
    {
        NONE    = 0x0,  // 无事件
        READ    = 0x1,  // 读事件(EPOLLIN)
        WRITE   = 0x4,  // 写事件(EPOLLOUT)   
    };

private:
    /**
     * @brief   socket事件上下文
     */
    struct FdContext
    {
        typedef Mutex MutexType;

        /**
         * @brief  事件上下文 
         */
        struct EventContext
        {
            Scheduler* scheduler = nullptr;
            Fiber::ptr fiber;
            std::function<void()> cb;
        };

        /**
         * @brief   获取对应事件的上下文
         */
        EventContext& getContext(Event event);
        
        void resetContext(EventContext& ctx);
        void triggerEvent(Event event);

        /// 读事件上下文
        EventContext read;
        /// 写事件上下文
        EventContext write;
        /// 描述符
        int fd = 0;
        /// 所含事件
        Event events = NONE;
        MutexType mutex;
    };

public:
    IOManager(size_t threads = 1, bool use_caller = true,  const std::string& name = "");
    ~IOManager();

    int addEvent(int fd, Event event, std::function<void()> cb = nullptr);
    bool delEvent(int fd, Event event);
    bool cancelEvent(int fd, Event event);
    bool cancelAll(int fd);

    static IOManager* GetThis();

protected:
    void tickle() override;
    bool stopping() override;
    void idle() override;
    void onTimerInsertedAtFront() override;

    void contextResize(size_t size);

    /**
     * @brief  判断是否可以停止 
     *
     * @param   timeout 最近要触发的定时器事件间隔
     */
    bool stopping(uint64_t& timeout);

private:
    int m_epfd;
    /// 用于tickele的pipe文件描述符,fd[0]是读端,fd[1]是写端
    int m_tickleFds[2];
    /// 正在等待执行的IO事件数量
    std::atomic<size_t> m_pendingEventCount = {0};
    RWMutexType m_mutex;
    /// socket事件上下文的容器
    std::vector<FdContext*> m_fdContexts;
};

}

#endif


#include "iomanager.h"
#include "macro.h"

#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

namespace sylar
{

static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

enum EpollCtlOp
{};

static std::ostream& operator<<(std::ostream& os, const EpollCtlOp& op)
{
    switch((int)op)
    {
#define XX(ctl) \
        case ctl: \
            return os << #ctl;
        XX(EPOLL_CTL_ADD);
        XX(EPOLL_CTL_MOD);
        XX(EPOLL_CTL_DEL);
        default:
            return os << (int)op;
    }
#undef XX
}

static std::ostream& operator<<(std::ostream& os, EPOLL_EVENTS events)
{
    if(!events)
    {
        return os << "0";
    }

    bool first = true;
#define XX(E) \
    if(events & E) \
    { \
        if(!first) \
        { \
            os << "|"; \
        } \
        os << #E; \
        first = false; \
    }
    XX(EPOLLIN);
    XX(EPOLLPRI);
    XX(EPOLLOUT);
    XX(EPOLLRDNORM);
    XX(EPOLLRDBAND);
    XX(EPOLLWRNORM);
    XX(EPOLLWRBAND);
    XX(EPOLLMSG);
    XX(EPOLLERR);
    XX(EPOLLHUP);
    XX(EPOLLRDHUP);
    XX(EPOLLONESHOT);
    XX(EPOLLET);
#undef XX
    return os;
}

IOManager::FdContext::EventContext& IOManager::FdContext::getContext(Event event)
{
    switch(event)
    {
        case IOManager::READ:
            return read;
        case IOManager::WRITE:
            return write;
        default:
            SYLAR_ASSERT2(false, "getContext");
    }
    throw std::invalid_argument("getContext invalid event");
}

void IOManager::FdContext::resetContext(EventContext& ctx)
{
    ctx.scheduler = nullptr;
    ctx.fiber.reset();
    ctx.cb = nullptr;
}

void IOManager::FdContext::triggerEvent(Event event)
{
    SYLAR_ASSERT(events & event);           // 触发的事件必须是先存在的
    events = (Event)(events & ~event);      // 去掉要触发的事件
    EventContext& ctx = getContext(event);  // 拿到要触发的事件上下文,将它放入调度器里面去
    if(ctx.cb)
    {
        ctx.scheduler->schedule(&ctx.cb);
    }
    else
    {
        ctx.scheduler->schedule(&ctx.fiber);
    }
    ctx.scheduler = nullptr;
    return;
}

IOManager::IOManager(size_t threads, bool use_caller,  const std::string& name)
    : Scheduler(threads, use_caller, name)
{
    m_epfd = epoll_create(5000);
    SYLAR_ASSERT(m_epfd > 0);

    // 创建pipe,获取m_tickeleFds[2],其中[0]是管道的读端,[1]是管道的写端
    int rt = pipe(m_tickleFds);
    SYLAR_ASSERT(!rt);

    // 注册pipe的读事件,ET模式。通过data.fd保存文件描述符
    epoll_event event;
    memset(&event, 0, sizeof(epoll_event));
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = m_tickleFds[0];

    // 设置为非阻塞,配合ET模式使用
    rt = fcntl(m_tickleFds[0], F_SETFL, O_NONBLOCK);
    SYLAR_ASSERT(!rt);

    rt = epoll_ctl(m_epfd, EPOLL_CTL_ADD, m_tickleFds[0], &event);
    SYLAR_ASSERT(!rt);

    contextResize(32);

    // 创建即可调度协程
    start();
}

IOManager::~IOManager()
{
    stop();
    close(m_epfd);
    close(m_tickleFds[0]);
    close(m_tickleFds[1]);

    for(size_t i=0; i<m_fdContexts.size(); ++i)
    {
        if(m_fdContexts[i])
        {
            delete m_fdContexts[i];
            m_fdContexts[i] = nullptr;
        }
    }
}

int IOManager::addEvent(int fd, Event event, std::function<void()> cb)
{
    FdContext* fd_ctx = nullptr;
    RWMutexType::ReadLock lock(m_mutex);
    if((int)m_fdContexts.size() > fd)
    {
        fd_ctx = m_fdContexts[fd];
        lock.unlock();
    }
    else
    {
        lock.unlock();
        RWMutexType::WriteLock lock2(m_mutex);
        contextResize(fd * 1.5);
        fd_ctx = m_fdContexts[fd];
    }

    FdContext::MutexType::Lock lock2(fd_ctx->mutex);
    // 同一个fd不允许重复添加相同的事件
    if(SYLAR_UNLIKELY(fd_ctx->events & event))
    {
        SYLAR_LOG_ERROR(g_logger) << "addEvent assert fd=" << fd
            << " event=" << (EPOLL_EVENTS)event
            << " fd_ctx.event=" << (EPOLL_EVENTS)fd_ctx->events;
        SYLAR_ASSERT(!(fd_ctx->events & event));
    }

    int op = fd_ctx->events ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
    epoll_event epevent;
    epevent.events = EPOLLET | fd_ctx->events | event;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if(rt)
    {
        SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "
            << (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"
            << rt << " (" << errno << ") (" << strerror(errno) << ") fd_ctx->events="
            << (EPOLL_EVENTS)fd_ctx->events;
        return -1;
    }

    ++m_pendingEventCount;      // 待执行IO事件数加1

    fd_ctx->events = (Event)(fd_ctx->events | event);

    // 找到这个fd的event事件对应的EventContext,对其中的scheduler、cb或fiber进行赋值
    FdContext::EventContext& event_ctx = fd_ctx->getContext(event);
    SYLAR_ASSERT(!event_ctx.scheduler
              && !event_ctx.fiber
              && !event_ctx.cb);

    event_ctx.scheduler = Scheduler::GetThis();
    if(cb)
    {
        event_ctx.cb.swap(cb);
    }
    else
    {
        event_ctx.fiber = Fiber::GetThis();
        SYLAR_ASSERT2(event_ctx.fiber->getState() == Fiber::EXEC,
                "state=" << event_ctx.fiber->getState());
    }
    return 0;
}

bool IOManager::delEvent(int fd, Event event)
{
    RWMutexType::ReadLock lock(m_mutex);
    if((int)m_fdContexts.size() <= fd)
    {
        return false;
    }

    FdContext* fd_ctx = m_fdContexts[fd];
    lock.unlock();

    FdContext::MutexType::Lock lock2(fd_ctx->mutex);
    if(SYLAR_UNLIKELY(!(fd_ctx->events & event)))
    {
        return false;
    }

    Event new_events = (Event)(fd_ctx->events & ~event);
    int op = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events = EPOLLET | new_events;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if(rt)
    {
        SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "
            << (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"
            << rt << " (" << errno << ") (" << strerror(errno) << ")";
        return false;
    }

    --m_pendingEventCount;      // 待执行事件减1

    // 重置该fd对应的event事件上下文
    fd_ctx->events = new_events;
    FdContext::EventContext& event_ctx = fd_ctx->getContext(event);
    fd_ctx->resetContext(event_ctx);
    return true;
}

/**
 * @brief  取消事件:如果该事件被注册过回调,那就触发一次回调事件 
 */
bool IOManager::cancelEvent(int fd, Event event)
{
    RWMutexType::ReadLock lock(m_mutex);
    if((int)m_fdContexts.size() <= fd)
    {
        return false;
    }

    FdContext* fd_ctx = m_fdContexts[fd];
    lock.unlock();

    FdContext::MutexType::Lock lock2(fd_ctx->mutex);
    if(SYLAR_UNLIKELY(!(fd_ctx->events & event)))
    {
        return false;
    }

    Event new_events = (Event)(fd_ctx->events & ~event);
    int op = new_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events = EPOLLET | new_events;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if(rt)
    {
        SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "
            << (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"
            << rt << " (" << errno << ") (" << strerror(errno) << ")";
        return false;
    }

    // 触发一次事件
    fd_ctx->triggerEvent(event);
    --m_pendingEventCount;
    return true;
}

bool IOManager::cancelAll(int fd)
{
    RWMutexType::ReadLock lock(m_mutex);
    if((int)m_fdContexts.size() <= fd)
    {
        return false;
    }

    FdContext* fd_ctx = m_fdContexts[fd];
    lock.unlock();

    FdContext::MutexType::Lock lock2(fd_ctx->mutex);
    if(!fd_ctx->events)
    {
        return false;
    }

    int op = EPOLL_CTL_DEL;
    epoll_event epevent;
    epevent.events = 0;
    epevent.data.ptr = fd_ctx;

    int rt = epoll_ctl(m_epfd, op, fd, &epevent);
    if(rt)
    {
        SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "
            << (EpollCtlOp)op << ", " << fd << ", " << (EPOLL_EVENTS)epevent.events << "):"
            << rt << " (" << errno << ") (" << strerror(errno) << ")";
        return false;
    }

    // 触发全部已注册的事件
    if(fd_ctx->events & READ)
    {
        fd_ctx->triggerEvent(READ);
        --m_pendingEventCount;
    }
    if(fd_ctx->events & WRITE)
    {
        fd_ctx->triggerEvent(WRITE);
        --m_pendingEventCount;
    }

    SYLAR_ASSERT(fd_ctx->events == 0);
    return true;
}

IOManager* IOManager::GetThis()
{
    return dynamic_cast<IOManager*>(Scheduler::GetThis());
}

/**
 * @brief   通知调度器有任务要调度
 *          写pipe让idle协程从epoll_wait退出,
 *          等idle协程yield之后,Scheduler::run就可以调度其他任务了
 */
void IOManager::tickle()
{
    // 如果当前没有空闲的调度线程,则直接return掉
    if(!hasIdleThreads())
    {
        return;
    }

    int rt = write(m_tickleFds[1], "T", 1);
    SYLAR_ASSERT(rt == 1);
}

bool IOManager::stopping()
{
    uint64_t timeout = 0;
    return stopping(timeout);
}

/**
 * @brief   idle退出的时机是epoll_wait返回,对应的操作是tickle或注册的IO事件就绪
 *
 */
void IOManager::idle()
{
    SYLAR_LOG_DEBUG(g_logger) << "idle";
    // 一次epoll_wait最多检测256个就绪事件,如果就绪事件超过了这个数,那么会在下轮epoll_wait继续处理
    const uint64_t MAX_EVENTS = 256;
    epoll_event* events = new epoll_event[MAX_EVENTS]();
    std::shared_ptr<epoll_event> shared_events(events, [](epoll_event* ptr)    // 自定义删除函数
    {
        delete[] ptr;
    });

    while(true)
    {
        uint64_t next_timeout = 0;
        if(SYLAR_UNLIKELY(stopping(next_timeout)))
        {
            SYLAR_LOG_INFO(g_logger) << "name=" << getName()
                                     << " idle stopping exit";
            break;
        }

        int rt = 0;
        do
        {
            static const int MAX_TIMEOUT = 3000;
            // 选择小的那个时间
            if(next_timeout != ~0ull)
            {
                next_timeout = (int)next_timeout > MAX_TIMEOUT ? MAX_TIMEOUT : next_timeout;
            }
            else
            {
                next_timeout = MAX_TIMEOUT;
            }

            rt = epoll_wait(m_epfd, events, MAX_EVENTS, (int)next_timeout);
            if(rt < 0 && errno == EINTR)
            {}
            else
            {
                break;
            }
        }while(true);

        std::vector<std::function<void()> > cbs;
        listExpiredCb(cbs);     // 拿到所有到期的定时任务
        if(!cbs.empty())
        {
            schedule(cbs.begin(), cbs.end());
            cbs.clear();
        }

        for(int i=0; i<rt; ++i)
        {
            epoll_event& event = events[i];
            if(event.data.fd == m_tickleFds[0])
            {
                // tickeleFd[0]用于通知协程调度,
                // 这时只需要把管道里的内容读完即可,
                // 本轮idle结束后,Scheduler::run会重新执行协程调度
                uint8_t dummy[256];
                while(read(m_tickleFds[0], dummy, sizeof(dummy)) > 0);
                continue;
            }

            FdContext* fd_ctx = (FdContext*)event.data.ptr;
            FdContext::MutexType::Lock lk(fd_ctx->mutex);
            // EPOLLERR 出错,比如写读端以及关闭的pipe
            // EPOLLHUP 套接字对端关闭
            // 出现这两种事件时,应该同时触发fd的读和写事件,否则有可能出现注册的事件永远执行不到的情况
            if(event.events & (EPOLLERR | EPOLLHUP))
            {
                event.events |= (EPOLLIN | EPOLLOUT) & fd_ctx->events;
            }

            int real_events = NONE;
            if(event.events & EPOLLIN)
            {
                real_events |= READ;
            }
            if(event.events & EPOLLOUT)
            {
                real_events |= WRITE;
            }

            if((fd_ctx->events & real_events) == NONE)  // 没有发生所存的事件,则continue
            {
                continue;
            }

            // 剔除已经发生的事件,将剩下的事件重新加入epoll_wait
            int left_events = (fd_ctx->events & ~real_events);
            int op = left_events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
            event.events = EPOLLET | left_events;
            
            int rt2 = epoll_ctl(m_epfd, op, fd_ctx->fd, &event);
            if(rt2)
            {
                SYLAR_LOG_ERROR(g_logger) << "epoll_ctl(" << m_epfd << ", "
                    << (EpollCtlOp)op << ", " << fd_ctx->fd << ", " << (EPOLL_EVENTS)event.events << "):"
                    << rt2 << " (" << errno << ") (" << strerror(errno) << ")";
                continue;
            }

            // 处理已经发送的事件,也就是让调度器调度指定的函数或协程
            if(real_events & READ)
            {
                fd_ctx->triggerEvent(READ);
                --m_pendingEventCount;
            }
            if(real_events & WRITE)
            {
                fd_ctx->triggerEvent(WRITE);
                --m_pendingEventCount;
            }
        }

        // 一旦处理完所有的事件,idle协程切出,这样就可以让调度协程Scheduler::run重新检查是否有新任务要调度
        Fiber::ptr cur = Fiber::GetThis();
        auto raw_ptr = cur.get();
        cur.reset();

        raw_ptr->swapOut();
    }
}

/**
 * @brief   有定时事件插入到定时器的最前面,则马上tickle一下
 */
void IOManager::onTimerInsertedAtFront()
{
    tickle();
}

void IOManager::contextResize(size_t size)
{
    m_fdContexts.resize(size);

    for(size_t i=0; i<m_fdContexts.size(); ++i)
    {
        if(!m_fdContexts[i])
        {
            m_fdContexts[i] = new FdContext;
            m_fdContexts[i]->fd = i;
        }
    }
}

bool IOManager::stopping(uint64_t& timeout)
{
    timeout = getNextTimer();           // 获取下一个定时器的超时时间
    return timeout == ~0ull
        && m_pendingEventCount == 0     // 所有IO事件都完成调度
        && Scheduler::stopping();
}

}

广告时间:基于sylar框架实现的小demo(希望给个star)

基于redis的参数查询服务

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值