UI 编程中,我们需要对鼠标点击,键盘按键等进行响应。这些用户操作在程序中被称为事件 (消息)。我们如何获得事件呢?

用一个线程轮询检测

事件相对于整个运行阶段其实是非常时间稀疏的,但这样一个线程一直轮询相当于在整个运行期间大部分是做无用功,造成大量的 CPU 资源浪费。

理想状况时,每个事件都有一个回调函数,这个函数就是操作的响应。由事件去通知主线程调用回调函数。有点类似于协程的原理。我们通过事件驱动模型来实现。

事件驱动模型

事件驱动模型有几大要素:

  • 一个事件 (消息) 队列
  • 每当有一个事件发生,就往队列中增加一个事件 (消息)
  • 一个事件循环,它不断从队列中取出事件,根据事件来调用相应回调函数。例如鼠标点击对应的 onclick (),键盘敲击对应的 onKeyDown () 等。
  • 每个事件 (消息) 都保存自己的回调函数指针 ,这样每个消息都有独立的处理函数。

单线程、多线程与事件循环

我们不难发现,不同于单线程的固定程序执行流和多线程的并行 / 并发执行流, 事件驱动编程范式 的程序执行流取决于外部事件。由事件循环来控制程序执行流。 它是典型的异步编程

单线程程序编写最简单,任务按照固定的顺序执行,当某个任务发生了阻塞,其他的任务也必须等待。直到它完成之后它们才能依次执行。即使各个任务并没有相互依赖的关系。可以理解为几个人在同一个银行窗口排队取款,每个人都必须等待前面的人取好款才能开始取款。

多线程程序编写比较复杂,任务在独立的线程上运行,这些线程是操作系统管理,如果 CPU 是单核,那么这些线程是并发执行,如果 CPU 是多核,那么这些线程可以并行执行。这种方法由于把各个线程的阻塞时间段重叠了,提升了程序执行效率。但多线程程序必须面临线程安全问题,多个线程需要处理好共享资源的问题。可以理解为几个人在不同的银行窗口取款,他们同步办理取款业务,但银行只有一台验钞机(共享资源),所以各个人的处理进度都与验钞机的分配有关。

事件驱动程序中,多个任务交错在一个线程执行,当等待 I/O 等阻塞操作时,它们注册一个回调到事件队列中,然后阻塞操作完成后,继续执行。回调描述了如何处理某个事件。事件循环轮询事件队列中的事件,并调用回调函数。它比多线程程序更直观,程序员无需关心线程安全问题。可以理解为可以理解为几个人在同一个银行窗口排队取款,前面有一个人需要去洗手间,那么窗口开始处理下一个人的业务,等钱一个人回来,再继续处理他的业务。

何时使用事件循环

  • 程序中的多个任务高度独立,不互相通信或依赖
  • 等待事件到来之前,任务会阻塞。