一个服务器程序,一般都需要处理三类事件: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();
}