理解事件循环
为了理解事件循环的概念,我们需要理解构成其内部结构的元素。
我们将使用术语资源描述符来代指套接字描述符和文件描述符。
轮询函数
轮询技术由不同的操作系统实现,旨在监视一个或多个资源描述符的状态,且轮询技术由系统功能负责实现。 轮询函数构成了事件循环的基础,我们经常发现这些模型被称为准备就绪通知方案(RN - Readiness Notification),因为轮询功能通知对事件感兴趣的程序,同时资源描述符也已准备好进行交互; 然而,感兴趣的程序可能会或不会完成所需的操作。
例如,在 Linux 方面,我们有以下轮询函数:
-
select()
: POSIX 实现存在一些缺点,如下所示:- 要监视的资源描述符数量有限制。
- 复杂度为
O(n)
,其中n
表示连接的客户端数量,这使得服务器无法同时处理多个客户端。
-
poll()
:select()
的增强版,有以下特点:- 允许监视更大范围的资源描述符
- 和
select()
一样的O(n)
时间复杂度 - 允许更多类型的监控事件
- 和
select()
相比比,可以复用entry数据
-
epoll()
: 这是一个强大的 Linux 实现,具有恒定时间复杂度O(1)
的吸引人的特性。epoll()
函数提供了两种行为来通过 epoll_wait() 调用来监视事件。 为了定义这两种行为,让我们想象这样一种场景:生产者在套接字(具有关联的套接字描述符)中写入数据,而消费者等待完成数据读取: - 水平触发(Level-triggered):当消费者完成对
epoll_wait()
的调用时,它将立即获得该资源描述符的状态并返回给请求的事件,指示执行读取操作的可能性(在我们的例子中)。 因此,水平触发的行为与事件的状态直接相关,而不是事件本身。 - 边缘触发(Edge-triggered):只有当套接字中的写入事件结束并且数据可用时,对
epoll_wait()
的调用才会返回。 因此,在边缘触发的行为中,重点是事件本身已经发生,而不是执行任何事件的可能性。
在其他平台上,也有可用的轮询功能,例如用于 BSD 和 Mac OS X 的 kqueue
。
轮询函数对于创建具有可以并发方式管理多个操作的单个线程的应用程序很有用。 例如,Tornado Web 服务器是使用非阻塞 I/O 编写的,作为轮询功能,它分别支持 epoll
和 kqueue for Linux
和 BSD/Mac OS X
。
轮询函数工作步骤如下:
- 一个
poller
对象被创建. - 我们可以在
poller
中注册或不注册一个或多个资源描述符。 - 轮询函数在创建的
poller
对象中执行。
Poller
是一个提供使用轮询方法的抽象接口
使用事件循环
我们可以将事件循环定义为简化版的使用轮询函数监视事件的抽象。 在内部,事件循环使用poller
对象,消除了程序员控制添加、删除和控制事件的任务的责任。
事件循环,一般来说,利用回调函数来处理事件的发生; 例如,给定一个资源描述符A,当A中发生写事件时,会有一个回调函数。 下面列举一些用Python实现事件循环的应用示例:
- Tornado web server ( http://www.tornadoweb.org/en/stable/ )
- Twisted ( https://twistedmatrix.com/trac/ )
- asyncio ( https://docs.python.org/3.4/library/asyncio.html )
- Gevent ( http://www.gevent.org/ )
- Eventlet ( https://pypi.python.org/pypi/eventlet )
创建日期: 2023年2月27日