ZLToolKit源码阅读:事件轮询器EventPoller

1059 篇文章 275 订阅

一个服务器程序,一般都需要处理三类事件:IO事件、定时事件、信号。

  • 为方便处理,我们需要统一事件源,比如使用IO复用来管理所有事件。
  • 其次,为了实现跨平台,我们需要提供一个平台无关的接口,与平台相关的实现在内部完成。比如对于IO复用,linux下有epoll,windows等其它平台有select,这里我们就可以统一封装,对外提供一致的接口。
  • 一个事件管理接口,还应该支持多线程

ZLToolKit的事件管理类是EventPoller,支持定时事件、IO事件处理,通过内置的管道事件,实现多线程的执行任务的负载均衡

在这里插入图片描述

  • EventPoller类接口大致可以分为四类:内部管道事件、定时器事件、用户事件(用户可以自己管理的事件,比如管道、socket等)以及线程相关接口
  • EventPoller的事件处理模式为Reactor模式,每一个事件都有其对应的回调,在事件触发后,该回调被调用
  • EventPoller的事件管理一般在单独的线程中进行,这可以通过runLoop函数确定。该接口的第一个参数可以设置为是否为非阻塞的,非阻塞状态下,会创建一个线程,后继的事件监听在线程中进行。linux系统下,事件监听通过epoll管理,非linux平台,通过select管理

分析

内部管道事件

内部管道事件用于用户工作线程和事件监听线程之间的通信。

  • 事件监听线程中,监听了管道的读端
  • 用户工作线程中,使用了管道的写端

基于该管道事件,实现了一个任务队列,可以在该线程中异步的执行任务。

  • _list_task用于存储和管道关联的任务
  • async_l负责将任务加入到_list_task中,然后向管道中写入数据,触发其读事件
  • runLoop中监听到该事件后,调用onPipeEvent来执行并清除_list_task中的任务

从构造函数说起构造函数

第一个值得探讨的是: 友元与继承

  • 该类虽然是一个单例类(构造函数私有),但是由于其将TaskExecutorGetterImp作为友元类,所以,使用时,我们一般不直接实例化该类对象,
    • 而是通过其友元类EventPollerPool来间接使用。
    • 在EventPollerPool中,会根据用户指定的size或者CPU核心数创建多个EventPoller的实例,后继使用时,根据特定条件选择其中一个来调用
public:
    friend class TaskExecutorGetterImp;  //友元关系不能继承(你朋友的孩子不是你的朋友)。这意味着从 YourOtherClass 派生的类不能访问 YourClass 的私有成员
private:
    /**
     * 本对象只允许在EventPollerPool中构造
     */
    EventPoller(ThreadPool::Priority priority = ThreadPool::PRIORITY_HIGHEST);
size_t TaskExecutorGetterImp::addPoller(const string &name, size_t size, int priority, bool register_thread) {
    size = size > 0 ? size : thread::hardware_concurrency();
    for (size_t i = 0; i < size; ++i) {
        EventPoller::Ptr poller(new EventPoller((ThreadPool::Priority) priority));
        poller->runLoop(false, register_thread);
        .......
    }
    return size;
}


class WorkThreadPool:  public TaskExecutorGetterImp {
	WorkThreadPool::WorkThreadPool() {
   	 //最低优先级
   		 addPoller("work poller", s_pool_size, ThreadPool::PRIORITY_LOWEST, false);
	}
}

class EventPollerPool :  public TaskExecutorGetterImp {
	EventPollerPool() {
	    auto size = addPoller("event poller", s_pool_size, ThreadPool::PRIORITY_HIGHEST, true);
	    InfoL << "创建EventPoller个数:" << size;
	}
}

在这里插入图片描述
结论:

  • 友元关系虽然不能传递,但是父类可以提供一个函数,让子类间接的访问不能实例化的另一个类。
  • 对应测试函数如下

class EventPoller {
public:
    friend class TaskExecutorGetterImp;  //友元关系不能继承(你朋友的孩子不是你的朋友)
private:
    EventPoller() = default;
};

class TaskExecutorGetter {
public:
    virtual ~TaskExecutorGetter() = default;
    virtual size_t getExecutorSize() const = 0;
};


class TaskExecutorGetterImp : public TaskExecutorGetter {
public:
    size_t getExecutorSize() const override{
        return 1;
    }
    
    size_t addPoller(const string &name) {
        std::shared_ptr<EventPoller> poller(new EventPoller());
        return 0;
    }
};
class WorkThreadPool:  public TaskExecutorGetterImp {
   WorkThreadPool() {
        addPoller("work poller");
    }
};
class EventThreadPool:  public TaskExecutorGetterImp {
    EventThreadPool() {
        addPoller("event poller");
    }
};

构造函数中干了写啥

大致分为两部分

(1)初始化并设置成员

  • 设置管道的读写端为非阻塞
    SockUtil::setNoBlocked(_pipe.readFD());
    SockUtil::setNoBlocked(_pipe.writeFD());
  • 初始化一个epoll
    _epoll_fd = epoll_create(EPOLL_SIZE);
    if (_epoll_fd == -1) {
        throw runtime_error(StrPrinter << "创建epoll文件描述符失败:" << get_uv_errmsg());
    }
    SockUtil::setCloExec(_epoll_fd);
  • 获取当前线程ID
    _loop_thread_id = this_thread::get_id();

(2)监听读事件

  • _pipe.readFD():监听的是管道的读端
  • Event_Read:监听的是读事件
  • [this](int event)......:当管道读事件被触发时,会执行onPipeEvent函数,注意event上面构造函数中初始化的各个成员都没有关系,具体干了写什么稍后研究
    //添加内部管道事件
    if (addEvent(_pipe.readFD(), Event_Read, [this](int event) { onPipeEvent(); }) == -1) {
        throw std::runtime_error("epoll添加管道失败");
    }

可以看到,管道的读端fd被添加到事件列表中,当管道读事件被触发时,会执行onPipeEvent函数。

addEvent有干了什么呢?

  • 首先,cb必须不为NULL,yes,上面是满足这个条件的
int EventPoller::addEvent(int fd, int event, PollEventCB cb) {
    if (!cb) {
        WarnL << "PollEventCB 为空!";
        return -1;
    }
  • 此时,走的是如下分支:
    • 将事件注册到epoll管理:_epoll_fd监听指定fd上的读事件(toEpoll(event))
    • 如果监听成功了,就用_event_map将fd和对应的回调关联起来。
      • 目的是当epoll_wait返回时,就可以知道fd对应的是哪个回调呢
      • 那epoll_wait什么时候调用呢?在runLoop()中。稍后解释runLoop
    if (isCurrentThread()) {
        struct epoll_event ev = {0};
        ev.events = (toEpoll(event)) | EPOLLEXCLUSIVE;
        ev.data.fd = fd;
        int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
        if (ret == 0) {
            _event_map.emplace(fd, std::make_shared<PollEventCB>(std::move(cb)));
        }
        return ret;
    }

runLoop有干了什么呢?

  • 不停的循环检测,如果epoll_wait上有事件发生,那么去_event_map找有没有对应的fd
  • 如果没有,那么不再监听这个fd
  • 如果有,就调用相应的回调函数。对于上面来说,将会调用onPipeEvent,我们来看看onPipeEvent干了写啥
      while (!_exit_flag) {
            ......
            int ret = epoll_wait(_epoll_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1);
            ......
            if (ret <= 0) {
                //超时或被打断
                continue;
            }
            for (int i = 0; i < ret; ++i) {
                struct epoll_event &ev = events[i];
                int fd = ev.data.fd;
                auto it = _event_map.find(fd);
                if (it == _event_map.end()) {
                    epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
                    continue;
                }
                auto cb = it->second;
                try {
                    (*cb)(toPoller(ev.events));
                } catch (std::exception &ex) {
                    ErrorL << "EventPoller执行事件回调捕获到异常:" << ex.what();
                }
            }
        }

onPipeEvent干了写啥

干了三件事:

  • 首先,读取管道中的数据,最多读取1024个字节
    // 不断循环,等待读到了字节之后才进行下一步
    char buf[1024];
    int err = 0;
    do {
        if (_pipe.read(buf, sizeof(buf)) > 0) {
            continue;
        }
        err = get_uv_error(true);
    } while (err != UV_EAGAIN);
  • 交换_list_task,即清空其中任务
    decltype(_list_task) _list_swap;
    {
        lock_guard<mutex> lck(_mtx_task);
        _list_swap.swap(_list_task);
    }
  • 执行_list_swap中所有任务。
  _list_swap.for_each([&](const Task::Ptr &task) {
        try {
            (*task)();
        } catch (ExitException &) {
            _exit_flag = true;
        } catch (std::exception &ex) {
            ErrorL << "EventPoller执行异步任务捕获到异常:" << ex.what();
        }
    });

_list_task的任务又是怎么来的呢?我们来看下async_l

如果是异步执行任务,那么将task添加到runLoop事件监听线程中调用onPipeEvent来执行任务。,然后通过向管道中写数据来触发读事件

private:
	Task::Ptr EventPoller::async_l(TaskIn task, bool may_sync, bool first) {   
	    auto ret = std::make_shared<Task>(std::move(task));
	    {
	        lock_guard<mutex> lck(_mtx_task);
	        if (first) {
	            _list_task.emplace_front(ret);
	        } else {
	            _list_task.emplace_back(ret);
	        }
	    }
	    //写数据到管道,唤醒主线程
	    _pipe.write("", 1);
	    return ret;
	}

在这里插入图片描述
可以看出,在构造函数中创建了一个监听pipe,用来监听pipe上的读事件,而pipe读事件是通过async_l触发的,async_l的作用是异步添加任务到任务队列_list_task中,并往pipe_中写一个字节。

async_l它是私有的,只能内部调用,又由哪些函数调用呢?

  • 用户可以通过async(task, false)async_first(task, false)addEvent(int fd, int event, PollEventCB cb)添加异步事件,这样就会触发上面的可读事件
// may_sync为true时,直接执行任务
// may_sync为false使,将任务压入async_l
Task::Ptr EventPoller::async(TaskIn task, bool may_sync) {
    return async_l(std::move(task), may_sync, false);  //false表示直接任务
}

Task::Ptr EventPoller::async_first(TaskIn task, bool may_sync) {
    return async_l(std::move(task), may_sync, true);   // true表示放到队列队头
}

int EventPoller::addEvent(int fd, int event, PollEventCB cb) {
    。。。。。。
    if (!cb) {
        WarnL << "PollEventCB 为空!";
        return -1;
    }

   ......

    async([this, fd, event, cb]() {
        addEvent(fd, event, std::move(const_cast<PollEventCB &>(cb)));
    });
    return 0;
}
  • shutdown将会打破上面的事件循环
void EventPoller::shutdown() {
    async_l([]() {
        throw ExitException();
    }, false, true);

    ......
}


  class ExitException : public std::exception {};

问题:为什么要析构函数中添加一个ExitException异常

inline void EventPoller::onPipeEvent() {
     _list_swap.for_each([&](const Task::Ptr &task) {
        try {
            (*task)();
        } catch (ExitException &) {
            _exit_flag = true;        //--------看这里---------
        } catch (std::exception &ex) {
            ErrorL << "EventPoller执行异步任务捕获到异常:" << ex.what();
        }
    });
}


void EventPoller::runLoop(bool blocked, bool ref_self) {
	.....
	 while (!_exit_flag) {
            .....
            int ret = epoll_wait(_epoll_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1);
           ......
        }

}

在这里插入图片描述

小结:addEvent干了写啥

两件事:

  • 监听异步任务,当有异步任务到来使被触发(private,只在EventPoller构造函数中调用)
  • 添加异步任务(用户可以调用这个来监听感兴趣的fd上的事件)
int EventPoller::addEvent(int fd, int event, PollEventCB cb) {
    TimeTicker();
    if (!cb) {
        WarnL << "PollEventCB 为空!";
        return -1;
    }

    if (isCurrentThread()) {
        struct epoll_event ev = {0};
        ev.events = (toEpoll(event)) | EPOLLEXCLUSIVE;
        ev.data.fd = fd;
        int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev);
        if (ret == 0) {
            _event_map.emplace(fd, std::make_shared<PollEventCB>(std::move(cb)));
        }
        return ret;
        auto record = std::make_shared<Poll_Record>();
        record->event = event;
        record->call_back = std::move(cb);
        _event_map.emplace(fd, record);
        return 0;
    }

    async([this, fd, event, cb]() {
        addEvent(fd, event, std::move(const_cast<PollEventCB &>(cb)));
    });
    return 0;
}

runLoop在哪些地方被调用

size_t TaskExecutorGetterImp::addPoller(const string &name, size_t size, int priority, bool register_thread) {
    for (size_t i = 0; i < size; ++i) {
        EventPoller::Ptr poller(new EventPoller((ThreadPool::Priority) priority));
        poller->runLoop(false, register_thread);
    }
    return size;
}


void EventPoller::runLoop(bool blocked, bool ref_self) {
    if (blocked) {
    	 .....
    	 while (!_exit_flag) {
            int ret = epoll_wait(_epoll_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1);
          ......
    }else{
		 _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self);
        _sem_run_started.wait();
	}
void EventPoller::runLoop(bool blocked, bool ref_self) {
    if (blocked) {
        ....
    }else{
		 _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self);
        _sem_run_started.wait();
	}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值