事件循环基础概念
2026/3/20大约 11 分钟
事件循环基础概念
什么是事件循环
基本定义
事件循环是一种程序设计模式,它持续等待和分发事件或消息。在计算机科学中,事件循环是一个编程结构,用于等待和分发程序中的事件或消息。
核心工作流程
事件循环的基本工作流程可以用伪代码表示:
while True:
# 1. 检查事件队列
if event_queue.has_events():
# 2. 取出事件
event = event_queue.dequeue()
# 3. 分发处理
handle_event(event)
else:
# 4. 等待新事件(通常会阻塞)
wait_for_events()
事件循环的三要素
| 要素 | 说明 | 示例 |
|---|---|---|
| 事件源 | 产生事件的来源 | 用户输入、网络 I/O、定时器、文件操作 |
| 事件队列 | 存储待处理事件的数据结构 | FIFO 队列、优先级队列 |
| 事件处理器 | 响应特定事件的回调函数 | onClick、onData、onTimeout |
为什么需要事件循环
传统同步编程的问题
在传统的同步编程模型中,程序按顺序执行,遇到 I/O 操作时会阻塞等待:
问题分析:
- 资源浪费:CPU 在等待 I/O 时处于空闲状态
- 响应延迟:用户操作需要等待前一个操作完成
- 扩展性差:每个并发连接都需要一个线程
多线程模型的挑战
为解决同步阻塞问题,传统方案是使用多线程:
⚠️ 问题:
- 线程创建/切换开销大
- 内存占用高(每线程 1MB+栈空间)
- 需要处理锁、死锁、竞态条件
- C10K 问题:万级连接时性能急剧下降
事件循环的优势
事件循环通过单线程 + 非阻塞 I/O实现高并发:
✅ 事件循环优势:
- 单线程无锁:避免竞态条件和死锁问题
- 内存占用低:无需为每个连接创建线程
- 充分利用 CPU:I/O 等待时处理其他任务
- 高并发处理:可轻松处理数万并发连接
事件循环的核心概念
1. 非阻塞 I/O
非阻塞 I/O 是事件循环的基础,它允许程序在等待 I/O 完成时执行其他任务:
# 阻塞式I/O(传统方式)
data = socket.recv(1024) # 阻塞,直到有数据到达
# 非阻塞式I/O(事件循环方式)
socket.setblocking(False)
try:
data = socket.recv(1024) # 立即返回
except BlockingIOError:
pass # 没有数据,稍后再试
2. 回调函数(Callback)
回调函数是事件循环处理异步操作结果的主要方式:
// 注册回调
fs.readFile("data.txt", (err, data) => {
// 这个函数在文件读取完成后被调用
if (err) {
console.error("读取失败:", err);
return;
}
console.log("文件内容:", data);
});
console.log("继续执行其他代码..."); // 这行先执行
3. 事件队列(Event Queue)
事件队列按照特定规则存储和调度待处理的事件:
4. 事件分发器(Event Dispatcher)
事件分发器负责将事件路由到正确的处理器:
class EventDispatcher:
def __init__(self):
self.handlers = {}
def on(self, event_type, handler):
"""注册事件处理器"""
if event_type not in self.handlers:
self.handlers[event_type] = []
self.handlers[event_type].append(handler)
def emit(self, event_type, *args, **kwargs):
"""触发事件"""
if event_type in self.handlers:
for handler in self.handlers[event_type]:
handler(*args, **kwargs)
# 使用示例
dispatcher = EventDispatcher()
dispatcher.on('data', lambda x: print(f'收到数据: {x}'))
dispatcher.emit('data', 'Hello, World!')
事件循环的工作阶段
一个完整的事件循环通常包含多个阶段:
各阶段详解
| 阶段 | 作用 | 典型场景 |
|---|---|---|
| Timers | 执行 setTimeout/setInterval 的回调 | 延迟执行、定时任务 |
| Pending I/O | 执行上一轮循环延迟的 I/O 回调 | 某些系统操作的回调 |
| Poll | 获取新的 I/O 事件,执行 I/O 相关回调 | 网络请求、文件读写 |
| Check | 执行 setImmediate 的回调 | 需要在 Poll 后立即执行的任务 |
| Close | 执行 close 事件的回调 | socket.on('close') |
事件循环 vs 其他并发模型
对比分析
| 特性 | 多线程 | 多进程 | 事件循环 |
|---|---|---|---|
| 内存开销 | 中等(每线程 1MB+栈) | 高(每进程完整地址空间) | 低(单线程) |
| 上下文切换 | 较高 | 最高 | 极低 |
| 编程复杂度 | 高(锁/同步) | 中等 | 中等(回调地狱) |
| CPU 密集型 | 适合 | 最适合 | 不适合 |
| I/O 密集型 | 一般 | 一般 | 最适合 |
| 代表技术 | Java Thread,C++ pthread | Python multiprocess | Node.js Nginx |
适用场景建议
事件循环的历史演进
发展时间线
简单实现一个事件循环
下面是一个简化版的事件循环实现,帮助理解其核心原理:
import heapq
import time
from collections import deque
class SimpleEventLoop:
"""简单的事件循环实现"""
def __init__(self):
self.ready = deque() # 就绪队列
self.timers = [] # 定时器堆(按触发时间排序)
self.running = False
def call_soon(self, callback, *args):
"""立即调度一个回调"""
self.ready.append((callback, args))
def call_later(self, delay, callback, *args):
"""延迟调度一个回调"""
trigger_time = time.time() + delay
heapq.heappush(self.timers, (trigger_time, callback, args))
def run_forever(self):
"""运行事件循环"""
self.running = True
while self.running:
# 1. 检查定时器,将到期的回调移入就绪队列
now = time.time()
while self.timers and self.timers[0][0] <= now:
_, callback, args = heapq.heappop(self.timers)
self.ready.append((callback, args))
# 2. 执行就绪队列中的所有回调
while self.ready:
callback, args = self.ready.popleft()
callback(*args)
# 3. 如果没有任务,休眠一小段时间
if not self.ready and not self.timers:
time.sleep(0.01)
elif self.timers:
# 休眠到下一个定时器触发
sleep_time = max(0, self.timers[0][0] - time.time())
time.sleep(min(sleep_time, 0.1))
def stop(self):
"""停止事件循环"""
self.running = False
# 使用示例
if __name__ == '__main__':
loop = SimpleEventLoop()
def greet(name):
print(f"Hello, {name}!")
def delayed_message():
print("这条消息延迟2秒显示")
loop.stop()
# 调度任务
loop.call_soon(greet, "World")
loop.call_later(1, greet, "Async")
loop.call_later(2, delayed_message)
print("启动事件循环...")
loop.run_forever()
print("事件循环结束")
本章小结
核心要点回顾
- 事件循环本质:一种持续等待和分发事件的编程模式
- 核心优势:单线程处理高并发,避免锁和竞态条件
- 适用场景:I/O 密集型应用,如 Web 服务器、API 网关
- 关键组件:事件队列、事件分发器、回调函数