高性能分布式网络服务器--IO协程调度器

IO协程调度器

在学习IO协程调度器之前必须掌握协程调度器,此外还需要对epoll相关接口非常熟悉,可以参考man 7 epoll

在协程调度模块中,调度器对协程的调度是无条件执行的,在调度器已经启动调度的情况下,任务一旦添加成功,就会排队等待调度器执行。调度器不支持删除调度任务,并且调度器在正常退出之前一定会执行完全部的调度任务。

IO协程调度器直接继承协程调度器实现,因此支持协程调度器的全部功能。此外IO协程调度还增加了IO事件调度的功能,这个功能针对socket fdIO协程调度支持为socket fd注册可读和可写事件的回调函数或协程,当描述符可读或可写时,执行对应的回调函数或协程

IO事件调度功能对服务器开发至关重要,因为服务器通常需要处理大量来自客户端的socket fd,使用IO事件调度可以将开发者从判断socket fd是否可读或可写的工作中解放出来,使得程序员只需要关心socket fdIO操作

github

https://github.com/huxiaohei/tiger.git

实现

tigerIO协程调度模块基于epoll实现,只支持Linux平台。

事件

对每个socket fd支持两类事件,一类是可读事件EPOLLIN,一类是可写事件EPOLLOUT。当然epoll本身除了支持了EPOLLINEPOLLOUT两类事件外,还支持EPOLLRDHUPEPOLLERREPOLLHUP等。为了简化操作,tiger是将所有事件全部归为EPOLLINEPOLLOUT中,也就是所有的事件都可以表示为可读或可写事件,甚至有的事件还可以同时表示可读及可写事件,比如EPOLLERR事件发生时,socket fd将同时触发可读和可写事件

enum EventStatus {
    NONE = 0x0,
    READ = 0x001,
    WRITE = 0x004
};

三元组信息

对于IO协程调度来说,每次调度都包含一个三元组信息(描述符-事件类型-回调信息),我们通过一个结构体Context来存储三原组信息。因为一个描述符可能同时注册读和写事件,因此分别使用readwrite属性来保存读和写的回调信息,从而在不同事件类型被触发时执行对应回调

typedef struct {
    struct EventContext {
        Scheduler::ptr scheduler;
        pid_t thread_id;
        Coroutine::ptr co;
        std::function<void()> cb;
    };
    MutexLock mutex;
    int fd = 0;
    EventStatus statuses = EventStatus::NONE;
    EventContext read;
    EventContext write;

    EventContext &get_event_context(const EventStatus status);
    void reset_event_context(EventContext &ctx);
    void trigger_event(const EventStatus status);
} Context;

注册事件

IO协程调度器会将全部的三元组信息存储在属性m_contexts中,m_contexts的类型为std::vectorIO调度器直接使用三元组的fd的值作为m_contexts数组的下标,这样可以快速找到一个fd对应的Context对象。由于关闭的fd会被重复利用,所以这里也不用担心m_contexts数组膨胀太快,或是利用率低的问题

IO协程调度器提供一个添加注册事件的接口bool IOManager::add_event(int fd, EventStatus status, std::function cb),接口需要指定fd,事件类型,回调函数三个参数。

首先我们通过fdm_contexts中取出一个Context对象(如果fd大于m_contexts的长度,表明m_contexts需要先进行扩容),然后使用statuscb两个参数来重新初始化Context对象

注册事件即向一个epoll实例中的一个fd添加一个epoll_event,然后使用epoll_event.data.ptr指针指向初始化好的Context对象,以便后续epoll_wait返回时能拿到fd的上下文信息即Context,并且执行其中的回调函数或协程

bool IOManager::add_event(int fd, EventStatus status, std::function<void()> cb) {
    ReadWriteLock::ReadLock rlock(m_lock);
    Context *ctx = nullptr;
    if ((int)m_contexts.size() > fd) {
        ctx = m_contexts[fd];
        rlock.unlock();
    } else {
        rlock.unlock();
        ReadWriteLock::WriteLock wlock(m_lock);
        fd_context_resize(fd * 1.5);
        ctx = m_contexts[fd];
    }
    MutexLock::Lock mlock(ctx->mutex);
    if (ctx->statuses & status) {
        TIGER_LOG_E(SYSTEM_LOG) << "[add_event fail"
                                << " statuses:" << ctx->statuses
                                << " status:" << status << "]";
        return false;
    }
    int op = ctx->statuses ? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
    epoll_event event;
    event.events = ctx->statuses | status | EPOLLET;
    event.data.ptr = ctx;
    if (epoll_ctl(m_epfd, op, fd, &event)) {
        TIGER_LOG_E(SYSTEM_LOG) << "[epoll_ctl fail"
                                << " epfd" << m_epfd
                                << " op" << op
                                << " fd" << fd
                                << " errno" << strerror(errno) << "]";
        return false;
    }
    ++m_appending_event_cnt;
    ctx->statuses = (EventStatus)(ctx->statuses | status);
    auto &event_context = ctx->get_event_context(status);
    event_context.scheduler = Scheduler::GetThreadScheduler();
    if (cb) {
        event_context.cb.swap(cb);
    } else {
        event_context.co = Coroutine::GetRunningCo();
        event_context.thread_id = Thread::CurThreadId();
    }
    return true;
}

删除事件

了解上面注册事件的流程后,删除事件其实与上面的流程非常的类似。这里就不过多的解释了。

bool IOManager::del_event(int fd, EventStatus status) {
    ReadWriteLock::ReadLock rlock(m_lock);
    if ((int)m_contexts.size() <= fd) {
        TIGER_LOG_E(SYSTEM_LOG) << "[del_event fail"
                                << " fd:" << fd
                                << " size:" << m_contexts.size() << "]";
        return false;
    }
    auto ctx = m_contexts[fd];
    if (!(ctx->statuses & status)) {
        TIGER_LOG_E(SYSTEM_LOG) << "[del_event fail"
                                << " fd:" << fd
                                << " statuses:" << ctx->statuses
                                << " status:" << status << "]";
        return false;
    }
    rlock.unlock();
    MutexLock mlock(ctx->mutex);
    EventStatus new_status = (EventStatus)(ctx->statuses & ~status);
    int op = new_status ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event event;
    event.events = EPOLLET | new_status;
    event.data.ptr = ctx;
    if (epoll_ctl(m_epfd, op, fd, &event)) {
        TIGER_LOG_E(SYSTEM_LOG) << "[epoll_ctl fail"
                                << " epfd:" << m_epfd
                                << " op:" << op
                                << " fd:" << fd
                                << " errno:" << strerror(errno) << "]";
        return false;
    }
    --m_appending_event_cnt;
    ctx->statuses = new_status;
    auto &event_context = ctx->get_event_context(new_status);
    ctx->reset_event_context(event_context);
    return true;
}

取消事件

取消事件与删除事件也非常的类似,区别只在于取消事件在删除epoll_event上的事件之后,会主动触发一次该事件的回调,在此也不过多解释

bool IOManager::cancel_event(int fd, EventStatus status) {
    ReadWriteLock::ReadLock rlock(m_lock);
    if ((int)m_contexts.size() <= fd) {
        TIGER_LOG_E(SYSTEM_LOG) << "[cancel_event fail"
                                << " fd:" << fd
                                << " size:" << m_contexts.size() << "]";
        return false;
    }
    auto ctx = m_contexts[fd];
    if (!(ctx->statuses & status)) {
        TIGER_LOG_E(SYSTEM_LOG) << "[cancel_event fail"
                                << " fd:" << fd
                                << " statuses:" << ctx->statuses
                                << " status:" << status << "]";
        return false;
    }
    rlock.unlock();

    MutexLock::Lock mlock(ctx->mutex);
    EventStatus new_status = (EventStatus)(ctx->statuses & ~status);
    int op = new_status ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
    epoll_event event;
    event.events = EPOLLET | new_status;
    event.data.ptr = ctx;
    if (epoll_ctl(m_epfd, op, fd, &event)) {
        TIGER_LOG_E(SYSTEM_LOG) << "[epoll_ctl fail"
                                << " epfd:" << m_epfd
                                << " op:" << op
                                << " fd:" << fd
                                << " errno:" << strerror(errno) << "]";
        return false;
    }
    --m_appending_event_cnt;
    ctx->trigger_event(status);
    return true;
}

idle协程

IO协程调度器为了解决协程调度器在idle状态下忙等待导致CPU占用率高的问题。IO协程调度器使用一对管道fdtickle调度协程,当调度器空闲时idle协程通过epoll_wait阻塞在管道的读描述符上,等待管道的可读事件。添加新任务时tickle方法写管道,idle协程检测到管道可读后退出,调度器执行调度

void IOManager::idle() {
    epoll_event *events = new epoll_event[256]();
    int rt = 0;
    std::vector<std::function<void()>> cbs;
    while (true) {
        rt = 0;
        do {
            static const int EPOLL_MAX_TIMEOUT = 5000;
            time_t next_time = next_timer_left_time();
            rt = epoll_wait(m_epfd, events, 256, next_time > EPOLL_MAX_TIMEOUT ? EPOLL_MAX_TIMEOUT : (int)next_time);
            if (rt > 0 || next_timer_left_time() <= 0 || is_stopping()) break;
        } while (true);
        all_expired_cbs(cbs);
        schedules(cbs.begin(), cbs.end());
        cbs.clear();
        for (int i = 0; i < rt; ++i) {
            epoll_event &event = events[i];
            if (event.data.fd == m_tickle_fds[0]) {
                uint8_t dummy;
                while (read(m_tickle_fds[0], &dummy, 1) == 1)
                    continue;
                continue;
            }
            Context *ctx = (Context *)event.data.ptr;
            MutexLock::Lock lock(ctx->mutex);
            if (event.events & (EPOLLERR | EPOLLHUP)) {
                event.events |= EPOLLIN | EPOLLOUT;
            }
            EventStatus real_status = EventStatus::NONE;
            if (event.events & EPOLLIN) {
                real_status = EventStatus::READ;
            }
            if (event.events & EPOLLOUT) {
                real_status = EventStatus::WRITE;
            }
            if ((EventStatus)(ctx->statuses & real_status) == EventStatus::NONE)
                continue;
            EventStatus left_status = (EventStatus)(ctx->statuses & ~real_status);
            int op = left_status ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
            event.events = EPOLLET | left_status;
            if (epoll_ctl(m_epfd, op, ctx->fd, &event)) {
                TIGER_LOG_E(SYSTEM_LOG) << "[epoll_ctl fail"
                                        << " epfd:" << m_epfd
                                        << " op:" << op
                                        << " fd:" << ctx->fd
                                        << " errno:" << strerror(errno) << "]";
                continue;
            }
            if (real_status & EventStatus::READ) {
                ctx->trigger_event(EventStatus::READ);
                --m_appending_event_cnt;
            }
            if (real_status & EventStatus::WRITE) {
                ctx->trigger_event(EventStatus::WRITE);
                --m_appending_event_cnt;
            }
        }
        {
            MutexLock::Lock lock(m_mutex);
            if (is_stopping() && m_tasks.size() == 0) {
                if (main_thread_id() == Thread::CurThreadId()) {
                    if (thread_cnt() == 0) break;
                } else {
                    break;
                }
            }
        }
        Coroutine::Yield();
    }
}

你可能感兴趣的:(C++高性能分布式网络服务器,服务器,网络,分布式)