在Golang的并发编程体系中,channel是goroutine之间通信的核心载体,基于它的阻塞和传递特性,可以很方便地实现事件通知功能,不需要依赖额外的第三方库,就能完成简单的事件分发与监听逻辑。
channel实现事件通知的基础原理
事件通知的核心逻辑是发布者将事件消息发送到通信载体,订阅者从载体中接收消息,channel正好满足这个需求。我们可以定义一个channel作为事件总线,当有事件触发时,向channel发送事件内容,监听该channel的goroutine就能收到通知并处理对应的逻辑。
最简单的单事件单订阅者实现如下:
package main
import (
"fmt"
"time"
)
func main() {
// 定义事件channel,传递字符串类型的事件内容
eventCh := make(chan string)
// 启动订阅者goroutine,监听事件
go func() {
for event := range eventCh {
fmt.Printf("收到事件通知: %sn", event)
}
}()
// 模拟事件触发,发布事件
time.Sleep(1 * time.Second)
eventCh <- "用户登录事件"
// 等待订阅者处理完成
time.Sleep(1 * time.Second)
}
多订阅者的事件通知实现
实际场景中往往需要一个事件对应多个订阅者,此时可以给每个订阅者分配一个独立的channel,当事件触发时,向所有订阅者的channel发送消息。为了避免发送时阻塞,通常会给订阅者channel设置缓冲大小。
下面是一个多订阅者的事件通知实现示例:
package main
import (
"fmt"
"sync"
)
// 事件总线结构体
type EventBus struct {
subscribers []chan string // 存储所有订阅者的channel
mu sync.RWMutex // 保证并发安全
}
// 创建新的事件总线
func NewEventBus() *EventBus {
return &EventBus{
subscribers: make([]chan string, 0),
}
}
// 订阅事件,返回订阅者的channel
func (eb *EventBus) Subscribe() chan string {
eb.mu.Lock()
defer eb.mu.Unlock()
ch := make(chan string, 10) // 缓冲大小为10,避免发送阻塞
eb.subscribers = append(eb.subscribers, ch)
return ch
}
// 发布事件,向所有订阅者发送消息
func (eb *EventBus) Publish(event string) {
eb.mu.RLock()
defer eb.mu.RUnlock()
for _, ch := range eb.subscribers {
ch <- event
}
}
// 关闭事件总线,释放所有channel
func (eb *EventBus) Close() {
eb.mu.Lock()
defer eb.mu.Unlock()
for _, ch := range eb.subscribers {
close(ch)
}
eb.subscribers = nil
}
func main() {
eb := NewEventBus()
defer eb.Close()
// 第一个订阅者
sub1 := eb.Subscribe()
go func() {
for event := range sub1 {
fmt.Printf("订阅者1收到事件: %sn", event)
}
}()
// 第二个订阅者
sub2 := eb.Subscribe()
go func() {
for event := range sub2 {
fmt.Printf("订阅者2收到事件: %sn", event)
}
}()
// 发布事件
eb.Publish("订单创建事件")
eb.Publish("支付成功事件")
// 等待订阅者处理
select {}
}
使用注意事项
channel关闭问题
如果使用for...range监听channel,一定要在不需要接收时关闭channel,否则会导致goroutine泄漏。上面的示例中我们在事件总线关闭时统一关闭所有订阅者的channel,订阅者就能正常退出循环。
阻塞处理
如果订阅者处理事件的速度慢于发布速度,无缓冲的channel会导致发布者阻塞,所以建议给订阅者的channel设置合适的缓冲大小,或者在发送时使用select加default分支,避免阻塞发布流程。
并发安全
事件总线的订阅和发布操作可能被多个goroutine同时调用,需要使用互斥锁或者读写锁保证操作的安全性,避免出现切片并发修改导致的错误。
适用场景
这种基于channel的事件通知实现适合轻量级的场景,比如单个服务内部的事件分发、模块间的松耦合通信。如果需要跨进程、持久化、更复杂的事件路由等功能,建议选择专业的消息队列组件。