even loop
在计算机科学中,事件循环是一种程序结构,或者说设计模式。 等待或者分发事件/消息的程序。
事件循环就是发送请求给事件提供者(可能是内部的,也可能是外部的), 一般会阻塞,直到事件处理完并返回,事件提供者会执行相应的事件处理。 事件处理也可以是消息分发,将消息分发给其他处理程序。
消息循环有时也被称为消息分发/消息循环/消息泵/运行循环。
如果事件处理程序遵循"文件接口规则"(unix的思想,一切皆文件), 那事件循环可以配合reactor(反应器设计模式)配合使用。 这样事件循环和消息发送者就是异步操作了。
消息泵是说泵里的消息会从一个程序的消息队列中传到一个执行程序中。 严格意义上讲,事件循环是ipc(进程间通讯)的一种方式。 很多系统都有消息处理。使用到消息传递的系统,都会有个消息循环的实现。
传统的,程序执行一次然后结束,是每天最常见的程序,她们可能仅仅是做一些计算, 缺乏用户交互,但她们使用非常频繁,是典型的命令行程序。每个参数都是提前设好, 在程序启动时传给程序。
菜单驱动设计,可能会有主循环,但不是常规意义上的事件驱动。 菜单驱动设计的交互性是有限的。
大多数用户界面程序都有一个主循环,操作系统会提供一个"获取下个消息的路由", 阻塞直到下个新消息来到。
unix口号:everything is a file.
这样在unix下就是基于文件的事件循环。 读文件/ipc/网络交互/设备控制都是利用文件i/o,任何对象都可以用文件句柄表示。 select和poll系统调用可以监控一系列文件句柄的变化状态。 (界面程序是由系统提供的"获取下个消息的路由"来实现事件循环, unix可通过select和poll系统调用来实现事件循环)。
在unix中,有少部分不在文件接口规则下,那就是异步事件。 我们将异步事件称为信号,信号是发给信号处理程序的, 信号处理程序很小,当任务挂起时,信号处理程序的代码就会运行。 在select中,如果任务在阻塞状态,又接到一个信号,那么select会返回一个EINTR的错误, 如果信号接收到时,任务处于cpu受限(cpu使用很高,任务的完成时间影响因子是cpu速度), 那任务会被挂起,先将信心处理程序执行完,再返回任务执行。
这样,为信号处理程序设置一个全局flag,在调用select()前后都都检查一下flag, 如果检查发现flag已设置(有信号来了),那就像用文件句柄处理事件一样处理信号。 不幸的是有可能发生竞争条件:检查完flag之后,调用select之前,信号来了, 那在select函数执行完之前,都无法处理信号了。
POSIX提出了一个解决方案,pselect系统调用,类似select,但多加了一个sigmask参数, 这个参数是一个信号掩码。程序在主任务中屏蔽信号,在select调用过程中解除屏蔽, 这样就能解决上面的竞争条件问题,确保在select中进行信号处理。 这种适合在i/o受限(此时cpu的负载比i/o好,cpu大部分时间在等i/o)的情况下使用。 linux版本2.6还未实现pselect,所以强迫glibc模拟了一个pselect。
当然还有其他解决方案,eg:用self-pipe将异步事件转换成基于文件接口的事件。 linux内核2.6中新增了一个signalfd的系统调用,利用这个系统调用, 通过一个指定的文件句柄,可以接收信号。
事件循环是i/o多路复用的一个例子。 不管是pselect,还是select,还是新增的signalfd,都是多路复用的一种实现。
windows中和用户有交互的处理逻辑都是有一个消息循环的。 windows上的消息循环就是事件循环,包含了用户交互/网络流量/系统处理/ 定时器/ipc等。非交互的只有i/o事件,widnows用i/o完成端口来解决。 i/o完成端口循环和消息循环是独立的。
windows上的win32 api就是一个典型的消息循环。多线程程序还会考虑到消息排序。
X windows系统上,要么是xlib事件循环,要么是glib事件循环。
macOS中用核心框架来实现循环。