浅谈“事件驱动”
1.通常,我们设计一个事件处理模型
的程序有两种思路:
- 一种是通过
轮询
的方式; - 一种通过
事件驱动
的方式,事件驱动
方式也被称为消息通知
方式。
下面举个简单收信
的例子,来说明两种实现方式的不同:
- (1) 传统的邮件,邮递员把它放到你家的邮箱里。因为你不知道什么时候有邮件,所以你要经常去检查邮箱,最近到底有没有邮件。这就是所谓的轮询方式,你要时常去检查,有没有发生事件发生,当你检查到有事件发生时,你采取相应措施,处理相关事件。
- (2)现代的电子邮件,你不用自己去查看邮箱,如果有新邮件,电脑会给你发消息,提示你有新邮件,然后你去查看邮箱。这就是所谓的事件驱动(消息通知),你不用去关心事件什么时候发生,当有事件发生时,会有人通知你,事件发生了,然后你再采取相应的措施,处理相关事件。
通过上面的例子,可以看出,轮询的最大的弊端在于,你要做许多无谓的检查,具体到程序中,就是会有CPU
资源的浪费,换言之,就是CPU
利用率不高。而事件驱动,就很好的解决了这个问题。不过事件驱动的缺点在于,模型较为复杂,程序写起来会比较复杂,但是,一旦掌握了事件驱动这种模型的基本设计思路,必定能达到事半功倍的效果。
2.网络服务器
设计过程中用到的 事件驱动模型
在这里我要说一说,通常的网络服务器
设计过程中的事件驱动模型
,希望能对需要帮助的朋友一点小小的帮助,这将是我很乐意看到的。
通常,高吞吐
、大并发
的网络服务器
,都会采用事件驱动模型
,其优点就是CPU利用率高
,写出来的程序效率比较高。
通常,我们会用select/poll/epoll
,这些由系统提供的I/O复用
的API
来实现事件驱动模型。
网络程序中的事件,通常可以分为三类:
- 一类是可读事件(数据已到达内核,上层应用可以调用相关读接口进行数据读取);
- 一类是可写事件(内核的写缓冲区有空余,上层应用可以调用相关写接口进行数据写入);
- 一类是异常(或出错)事件(发生了异常情况,需要进行异常处理)。
select
和poll
, 与epoll
相比,当并发数比较大的时候,性能差别会比较大,主要是因为内核通知事件的方式不同。
-
在
select
和poll
中,内核只是在通知有多少个fd
上发生了事件,并在相应的fd
上做了标记,上层应用要想知道具体是那个fd
上发生了什么事件,就必须去遍历整个fd
的集合。 -
而在
epoll
中,操作系统通知事件的方式是,把有事件发生的fd
集合返回给上层应用,这样就不需要再去做无谓的遍历了。尤其在并发数比较大(fd
集合中fd
个数多)的时候,它们之间的性能差别还是挺大的。因此,性能要求比较高的网络服务器的实现,大多都使用epoll
。
下图所示是一种比较常见的事件驱动(消息通知)模型的简单实现:
上图中,EventMonitor
是用来从操作系统获取事件的线程,EventProcessor
是实际来处理事件的线程。
在EventMonitor
中调用select
/poll
/epoll_wait
来获取事件,然后把发生的事件,以消息的方式通知给EventProcessor
,EventProcessor
收到消息后,处理所发生的事件。
这里EventProcessor
可以有多个,同一个fd
的上事件,一般在同一个EventProcessor
上处理。这样,EventMonitor
的工作就是获取事件,是比较轻量级的,重量级的运算处理工作都由EventProcessor
来处理,通过调整EventProcessor
的个数,提高CPU
的利用效率,从而提高整个程序的性能。