Go语言Channel无数据传输、触发死锁问题排查
Go简易事件驱动框架Channel异常、死锁问题排查
问题根因
两个异常本质是4处基础用法错误导致:
- 核心错误:
events/setup.go中仅声明了Publish chan string通道变量,未执行初始化操作。Go语言中channel的零值为nil,对nil通道执行发送、接收操作会永久阻塞,这也是死锁报错中chan send (nil chan)、chan receive (nil chan)提示的直接来源。 - for+select逻辑错误:带
default分支的select在通道无就绪数据时会直接执行default分支逻辑,原代码在default分支中调用time.Sleep(math.MaxInt64)相当于让主协程永久睡眠,后续就算通道有数据写入,也不会再被select监听,因此程序会一直输出等待提示。 - 通道遍历写法错误:
for x := range <-events.Publish是错误写法,<-events.Publish是尝试从通道接收单个值,range会将接收到的string类型值作为遍历对象,既不符合通道遍历的语法逻辑,也会因为通道是nil直接阻塞在接收步骤触发死锁。 - 附带参数传递bug:
Trigger方法中调用事件处理函数时未将可变参数打散,导致多参数传入时会被额外包装一层切片,和预期传参逻辑不符。
修复方案
- 初始化Publish通道:声明通道时直接通过
make完成初始化,根据业务场景选择缓冲大小,本次场景3个事件各发送1条消息,初始化容量为3的带缓冲通道即可避免发送方不必要的阻塞。 - 修正通道读取逻辑:删除default分支中永久睡眠的错误逻辑,使用标准
for range语法遍历通道,通道关闭后循环会自动退出。 - 修正WaitGroup用法:
Wg.Add调用必须放在启动子goroutine之前,避免Add和Wait产生竞态;单独起协程等待所有事件处理完成后主动关闭Publish通道,通知接收方所有数据发送完毕。 - 修正可变参数传递:调用事件处理Action时,对payload参数加
...打散,匹配可变参数的传参要求。
修正后完整代码
events/setup.go
//events/setup.go package events import "sync" var Wg sync.WaitGroup // 声明时直接初始化带缓冲通道 var Publish = make(chan string, 3) type Event struct { Name string Action func(...any) } type Events struct { handlers []Event } func (e *Events) register(name string, action func(...any)) { e.handlers = append(e.handlers, Event{ Name: name, Action: action, }) } func (e *Events) Trigger(name string, payload ...any) { for _, event := range e.handlers { if event.Name == name { // 可变参数打散后传入处理函数 event.Action(payload...) } } }
events/user.go
//events/user.go package events import "fmt" var User Events func init() { User.register("new", func(payload ...any) { fmt.Println(payload[0]) Publish <- "{'new':'done'}" Wg.Done() }) User.register("name", func(payload ...any) { for index, person := range payload { fmt.Println(person, index) } Publish <- "{'name':'done'}" Wg.Done() }) }
main.go
//main.go package main import ( "edriven/events" "fmt" ) func main() { fmt.Println("Starting") // Wg.Add放在goroutine启动前调用,避免竞态 events.Wg.Add(3) go events.User.Trigger("new", "Hasan") go events.User.Trigger("name", []any{"Hasan", "Ali"}) go events.User.Trigger("new", "Ali") // 等待所有事件处理完成后关闭通道 go func() { events.Wg.Wait() close(events.Publish) }() // 标准range遍历通道,通道关闭后自动退出 for x := range events.Publish { fmt.Println(x) } }
修复后程序可正常输出所有事件处理结果,无死锁、通道阻塞问题。
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

