事件驱动编程模式之EventLoop
事件驱动
优点:
- 松耦合,开发者可以像调用函数一样 解耦式使用它。
- 独立隔离,不同事件不互相影响
- 快速开发,不需要每次都为一段执行代码重新设计异步实现
- 降低复杂度
- 低消耗,节省cpu资源
- 异步使用场景广,适合复杂、高动态的工作流
缺点: - 难以测试,这也是事件的缺点:并发时序性每次都不完全一样,溯源调试复杂,代码上已经看不出顺序。尽管有trace方式,但无法完全一致地重放追溯。
- 集中式的分发,发起端单点故障容易发生中心雪崩式资源耗尽,一般高可行情况下需要配置限流和熔断。
EDA
云架构中常常是更大架构层面的EDA, Event-driven architecture事件驱动架构,是一种跨服务跨系统的分布式现代架构模式,基于发布-订阅的消息异步通讯架构。
Notes: EDA上架构层面的概念,与设计模式的EventLoop并不在同一层概念,但本质上都是事件驱动思想的衍生。
https://bytebytego.com/guides/how-do-message-queue-architectures-evolve/


局部图:

C的常见最小实现
Event -> EventLoop with Queue -> HandlerA,HandlerB,HanlderC...
EventLoop
对于小型程序来说,实现EDA的一种方式就是以EventLoop事件循环为中心,通过发布订阅的方式来实现微型EDA。
最小的EDA需要 EventPublisher-> Event ->EventLoop->EventHandlers
组成部分:
- 队列 + pthread_mutex + pthread_cond
- 一个专门处理事件的线程循环
- 发布者线程
- Handlers 回调
Event Loop 实现原理
数据结构
classDiagram
class EventQueue {
Event buffer[256]
int head
int tail
int size
pthread_mutex_t mutex
pthread_cond_t not_empty
pthread_cond_t not_full
}
class Event {
EventType type
int param1
int param2
void* data
}
class HandlerTable {
EventHandler handlers[EVENT_TYPE_COUNT]
+register_handler(type, fn)
+handle_event(e)
}
EventQueue o-- Event : "buffer 存储"
HandlerTable ..> Event : "按 type 分发"
style EventQueue fill:#2d6a4f,color:#fff,stroke:#1b4332
style Event fill:#264653,color:#fff,stroke:#1d3557
style HandlerTable fill:#e76f51,color:#fff,stroke:#c1440e
push 与 pop 的同步交互
sequenceDiagram
box rgb(42,84,122) 生产者
participant P as 生产者线程
end
box rgb(231,111,81) 同步原语
participant M as mutex
participant Q as queue
participant NE as cond not_empty
participant NF as cond not_full
end
box rgb(45,106,79) 消费者
participant C as event_loop_thread
end
Note over C: 🟢 while(running) 循环开始
C->>M: 🔒 pthread_mutex_lock()
Note over C: size==0, 队列空
C->>NE: 😴 pthread_cond_wait(not_empty, mutex)
Note over C: ⚠️ 原子地释放 mutex 并挂起
P->>M: 🔒 pthread_mutex_lock()
Note over P: 获得锁
P->>Q: 📝 buffer[tail] = event<br/>tail = (tail+1) % MAX<br/>size++
P->>NE: 📢 pthread_cond_broadcast(not_empty)
Note over NE: 唤醒所有等待者
P->>M: 🔓 pthread_mutex_unlock()
Note over C: ⏰ 被唤醒, 自动重新获得 mutex
Note over C: 🔄 while(size==0) 再检查<br/>防 spurious wakeup
C->>Q: 📤 event = buffer[head]<br/>head = (head+1) % MAX<br/>size--
C->>NF: 📢 pthread_cond_signal(not_full)
C->>M: 🔓 pthread_mutex_unlock()
Note over C: 🎯 handle_event → handlers[type](e)
Note over C: 🔁 回到 while 顶部
队列满时的反压
sequenceDiagram
box rgb(42,84,122) 生产者
participant P as 生产者线程
end
box rgb(231,111,81) 同步原语
participant M as mutex
participant Q as queue
participant NF as cond not_full
end
box rgb(45,106,79) 消费者
participant C as event_loop_thread
end
P->>M: 🔒 lock()
Note over P: size==MAX, 队列满
P->>NF: 😴 wait(not_full, mutex)
Note over P: ⚠️ 释放 mutex 并挂起
C->>M: 🔒 lock()
C->>Q: 📤 pop 事件, size--
C->>NF: 📢 signal(not_full)
C->>M: 🔓 unlock()
Note over P: ⏰ 被唤醒, 重获 mutex
Note over P: 🔄 while(size==MAX) 再检查
P->>Q: 📝 push 事件, size++
P->>M: 🔓 unlock()
event loop 主循环
flowchart TD
A["queue_pop(q, &e)"]
A1["🔒 lock(mutex)"]
A2["😴 while(size==0): wait(not_empty)"]
A3["📤 取出 buffer[head], size--"]
A4["📢 signal(not_full)"]
A5["🔓 unlock(mutex)"]
B{e.type == QUIT?}
C["📢 queue_push(q, &e)\n转发 QUIT"]
D["🛑 break"]
E["🎯 handle_event(&e)"]
E1["查 handlers[e.type]"]
E2["调用回调函数"]
A --> A1 --> A2 --> A3 --> A4 --> A5
A5 --> B
B -- Yes --> C --> D
B -- No --> E --> E1 --> E2 --> A
style A fill:#264653,color:#fff,stroke:#1d3557
style A1 fill:#e76f51,color:#fff,stroke:#c1440e
style A2 fill:#e9c46a,color:#000,stroke:#f4a261
style A3 fill:#2a9d8f,color:#fff,stroke:#21867a
style A4 fill:#e76f51,color:#fff,stroke:#c1440e
style A5 fill:#e76f51,color:#fff,stroke:#c1440e
style B fill:#f4a261,color:#000,stroke:#e76f51
style C fill:#264653,color:#fff,stroke:#1d3557
style D fill:#c1121f,color:#fff,stroke:#780000
style E fill:#2d6a4f,color:#fff,stroke:#1b4332
style E1 fill:#2d6a4f,color:#fff,stroke:#1b4332
style E2 fill:#2d6a4f,color:#fff,stroke:#1b4332
颜色含义
| 颜色 | 含义 |
|---|---|
| 🟧 橙/红 | mutex 操作 (lock / unlock / signal) |
| 🟨 黄 | 条件等待 (cond_wait) |
| 🟩 绿 | 事件分发 (handle_event / 回调) |
| 🟦 蓝 | 队列数据操作 (push / pop) |
堵塞改进 - 增加线程池
while (running) {
queue_pop(q, &e);
if (e.type == EVENT_QUIT) { ... break; }
- handle_event(&e);
+ thread_pool_submit(&pool, &e); // 异步派发,不阻塞
}
+ thread_pool_destroy(&pool); // loop 退出后等待 worker 完成
关键点
这个 event loop 的核心就是一个基于 mutex + 条件变量的有锁阻塞队列。
本质上整个模型可以归结为:
loop 线程阻塞在 queue_pop (锁 + cond_wait)
→ 有事件时被唤醒 (锁 + cond_signal/broadcast)
→ 取出事件 → 派发
→ 回去继续阻塞
| 问题 | 解法 |
|---|---|
| loop 空闲时如何不占 CPU | wait(not_empty) 挂起线程,由 push 端 broadcast 唤醒 |
| 队列满时生产者怎么办 | wait(not_full) 挂起,由 pop 端 signal 唤醒 |
为什么用 while 不用 if |
pthread 允许 spurious wakeup,醒来后必须重新检查条件 |
wait 时 mutex 怎么办 |
cond_wait 原子地释放 mutex 并挂起,被唤醒时自动重新加锁 |
| 为什么 not_empty 用 broadcast | 多消费者需唤醒所有等待者 |
| 为什么 not_full 用 signal | pop 只腾出一个位置,唤醒一个生产者即可 |
| (改进) 这里是单线程eventloop+单线程handler会堵塞loop,如何做多线程 | 新增线程池,然后将handle_event改为提交到线程池执行即可达到不堵塞loop的需求 |
| (改进) 用无锁队列实现呢 | 推荐eventfd+环形队列,利用内核的eventfd read堵塞完成调度 |
应用场景
- WEB服务器的路由组件(nginx/nodejs/AI Agent)
- 异步的内存数据处理(视频数据处理)
- GUI图形界面架构(Windows/QT/JWT/JavaFX/)
- 云服务架构的消息队列(基于Kafka/RabbitMQ的微服务架构)
本文来自博客园,作者:蓝天上的云℡,采用 BY-NC-SA 许可协议,转载请注明:转载自作者蓝天上的云℡ 原文链接 https://www.cnblogs.com/yucloud/p/19977902/c_eventloop



浙公网安备 33010602011771号