Go 语言没有内置的事件机制(如 C# 的 event、JavaScript 的 EventEmitter,但 完全可以通过标准库和惯用模式实现强大、类型安全、高性能的事件模型。是否“完整”取决于你的需求 —— 对于绝大多数场景,Go 的并发原语(channel + goroutine)足以构建比传统回调式事件系统更清晰、更可控的事件驱动架构。

✅ 一、Go 没有“官方事件”,但有更优解

语言事件机制Go 的替代方案
JavaScriptEventEmitterChannel + Select
C#event / delegatesChannel 广播 / Callback 注册
JavaObserver / Listener接口 + Channel / Pub-Sub

💡 Go 的哲学:用组合和并发原语解决问题,而非引入新概念。

✅ 二、Go 中实现事件模型的 4 种主流方式

方式 1️⃣:Channel 广播(最 Go 风格)

适用于:1 对多通知、流式事件

type EventBus struct {
    subscribers []chan Event
    mu          sync.RWMutex
}

func (eb *EventBus) Subscribe() <-chan Event {
    ch := make(chan Event, 1)
    eb.mu.Lock()
    eb.subscribers = append(eb.subscribers, ch)
    eb.mu.Unlock()
    return ch
}

func (eb *EventBus) Publish(event Event) {
    eb.mu.RLock()
    defer eb.mu.RUnlock()
    for _, ch := range eb.subscribers {
        // 非阻塞发送(防慢消费者阻塞)
        select {
        case ch <- event:
        default:
            // 可选:记录丢弃或关闭慢 consumer
        }
    }
}

优点

方式 2️⃣:回调注册(简单场景)

适用于:单次监听、同步处理

type EventHandler func(event Event)

type EventEmitter struct {
    handlers []EventHandler
}

func (e *EventEmitter) On(h EventHandler) {
    e.handlers = append(e.handlers, h)
}

func (e *EventEmitter) Emit(event Event) {
    for _, h := range e.handlers {
        h(event) // 同步调用
    }
}

⚠️ 缺点

🛠 改进:在 goroutine 中异步执行:

go func(h EventHandler, ev Event) { h(ev) }(h, event)

方式 3️⃣:基于 interface 的观察者模式

适用于:需要多态、解耦组件

type Observer interface {
    OnEvent(event Event)
}

type Subject struct {
    observers []Observer
}

func (s *Subject) AddObserver(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) Notify(event Event) {
    for _, o := range s.observers {
        o.OnEvent(event)
    }
}

适合:大型系统中模块间松耦合通信。

方式 4️⃣:使用第三方库(如 asaskevich/EventBus

如果你想要类似 JS 的 API:

import "github.com/asaskevich/EventBus"

bus := EventBus.New()
bus.Subscribe("user.login", func(user string) {
    fmt.Println("User logged in:", user)
})
bus.Publish("user.login", "alice")

⚠️ 但注意

🚫 官方建议:优先使用标准库原语。

✅ 三、Go 事件模型 vs 传统事件模型

特性传统事件(JS/C#)Go(Channel 方案)
类型安全❌(通常 dynamic)✅(强类型 channel)
背压处理❌(易内存爆炸)✅(buffered channel)
取消/超时⚠️(需额外设计)✅(天然支持 context)
并发安全⚠️(需手动加锁)✅(channel 天然同步)
调试难度⚠️(回调地狱)✅(goroutine 可追踪)

🎯 Go 的 channel 本质是一个“带队列的事件总线”,且更可控。

✅ 四、真实场景示例:WebSocket 会话事件

// 定义事件类型
type SessionEvent struct {
    Type string      // "text", "audio", "close"
    Data interface{}
}

type Session struct {
    events chan SessionEvent
    ctx    context.Context
}

func NewSession() *Session {
    ctx, cancel := context.WithCancel(context.Background())
    return &Session{
        events: make(chan SessionEvent, 10),
        ctx:    ctx,
    }
}

// 发布事件
func (s *Session) emitText(text string) {
    select {
    case s.events <- SessionEvent{"text", text}:
    case <-s.ctx.Done():
    }
}

// 监听事件(Client 使用)
func (c *Client) listenSessionEvents() {
    for {
        select {
        case ev := <-c.session.events:
            switch ev.Type {
            case "text":
                c.sendText(ev.Data.(string))
            case "close":
                return
            }
        case <-c.session.ctx.Done():
            return
        }
    }
}

这就是一个完整的、类型安全、可取消、带背压的事件系统!

✅ 五、何时需要“事件模型”?

场景是否推荐事件模型
组件间解耦(如 Hub → Client)✅ 是
状态变更通知(如用户登录)✅ 是
流式数据处理(如 LLM chunk)✅ 用 channel 更直接
简单单向调用❌ 直接函数调用即可

📌 总结

Go 没有内置事件机制,但它的并发原语(channel + goroutine + select)构成了一个更强大、更安全、更符合工程实践的“事件驱动”基础。

最佳实践建议:

  1. 优先使用 channel 广播(fan-out)实现 1→N 事件;
  2. 用具体类型代替 interface{},保持类型安全;
  3. 结合 context 实现取消
  4. 用 buffered channel 处理背压
  5. 避免过度设计 —— 如果只有 1~2 个监听者,直接函数调用更简单。

💡 记住:在 Go 中,channel 就是你的事件总线。

所以,Go 的事件模型不仅“完整”,而且更优雅、更高效 —— 只要你用对了方式。