Go 语言没有内置的事件机制(如 C# 的 event、JavaScript 的 EventEmitter),但 完全可以通过标准库和惯用模式实现强大、类型安全、高性能的事件模型。是否“完整”取决于你的需求 —— 对于绝大多数场景,Go 的并发原语(channel + goroutine)足以构建比传统回调式事件系统更清晰、更可控的事件驱动架构。
| 语言 | 事件机制 | Go 的替代方案 |
|---|---|---|
| JavaScript | EventEmitter | Channel + Select |
| C# | event / delegates | Channel 广播 / Callback 注册 |
| Java | Observer / Listener | 接口 + Channel / Pub-Sub |
💡 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
}
}
}
✅ 优点:
Event 可是具体结构体);适用于:单次监听、同步处理
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)
适用于:需要多态、解耦组件
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)
}
}
✅ 适合:大型系统中模块间松耦合通信。
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")
⚠️ 但注意:
interface{},失去类型安全;🚫 官方建议:优先使用标准库原语。
| 特性 | 传统事件(JS/C#) | Go(Channel 方案) |
|---|---|---|
| 类型安全 | ❌(通常 dynamic) | ✅(强类型 channel) |
| 背压处理 | ❌(易内存爆炸) | ✅(buffered channel) |
| 取消/超时 | ⚠️(需额外设计) | ✅(天然支持 context) |
| 并发安全 | ⚠️(需手动加锁) | ✅(channel 天然同步) |
| 调试难度 | ⚠️(回调地狱) | ✅(goroutine 可追踪) |
🎯 Go 的 channel 本质是一个“带队列的事件总线”,且更可控。
// 定义事件类型
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)构成了一个更强大、更安全、更符合工程实践的“事件驱动”基础。
interface{},保持类型安全;context 实现取消;💡 记住:在 Go 中,channel 就是你的事件总线。
所以,Go 的事件模型不仅“完整”,而且更优雅、更高效 —— 只要你用对了方式。