事件驱动编程模式之EventLoop

事件驱动

优点:

  • 松耦合,开发者可以像调用函数一样 解耦式使用它。
  • 独立隔离,不同事件不互相影响
  • 快速开发,不需要每次都为一段执行代码重新设计异步实现
  • 降低复杂度
  • 低消耗,节省cpu资源
  • 异步使用场景广,适合复杂、高动态的工作流
    缺点:
  • 难以测试,这也是事件的缺点:并发时序性每次都不完全一样,溯源调试复杂,代码上已经看不出顺序。尽管有trace方式,但无法完全一致地重放追溯。
  • 集中式的分发,发起端单点故障容易发生中心雪崩式资源耗尽,一般高可行情况下需要配置限流和熔断。

EDA

云架构中常常是更大架构层面的EDA, Event-driven architecture事件驱动架构,是一种跨服务跨系统的分布式现代架构模式,基于发布-订阅的消息异步通讯架构。
Notes: EDA上架构层面的概念,与设计模式的EventLoop并不在同一层概念,但本质上都是事件驱动思想的衍生。

https://bytebytego.com/guides/how-do-message-queue-architectures-evolve/

image

image

局部图:
image

C的常见最小实现

Event -> EventLoop with Queue -> HandlerA,HandlerB,HanlderC...

EventLoop

对于小型程序来说,实现EDA的一种方式就是以EventLoop事件循环为中心,通过发布订阅的方式来实现微型EDA。
最小的EDA需要 EventPublisher-> Event ->EventLoop->EventHandlers

组成部分:

  • 队列 + pthread_mutex + pthread_cond
    • image
  • 一个专门处理事件的线程循环
    • image
  • 发布者线程
  • 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的微服务架构)
posted @ 2026-05-06 14:28  蓝天上的云℡  阅读(4)  评论(0)    收藏  举报