所以我有一个用于事件处理的频道,主要服务器goroutine在这个频道上选择并在收到的每个事件上调用事件处理程序:
evtCh := make(chan Event)
// server loop:
for !quit {
select {
case e := <- evtCh:
handleEvent(e)
break
case quit := <-quitCh:
//finish
}
// for send a new event to processing
func addEvent(e Event) {
evtCh <- e
}
handleEvent
将在事件类型上调用注册处理程序。我有func registerEventHandler(typ EventType, func(Event))
来处理寄存器。该程序将支持用户编写扩展,这意味着他们可以注册自己的处理程序来处理事件。
现在问题出现在用户的事件处理程序中,他们可能通过调用addEvent
向服务器发送新事件,这将导致服务器挂起,因为事件处理程序本身在上下文中被调用服务器的主循环(在for循环中)。
我怎样才能优雅地处理这种情况?一个由切片建模的队列是个好主意吗?
答案 0 :(得分:3)
这将导致服务器挂起,因为在服务器主循环的上下文中调用了事件处理程序本身
主循环永远不应该在调用handleEvent
时阻塞,避免这种情况的最常见方法是使用一个worker goroutines池。这是一个快速未经测试的示例:
type Worker struct {
id int
ch chan Event
quit chan bool
}
func (w *Worker) start {
for {
select {
case e := <- w.ch:
fmt.Printf("Worker %d called\n", w.id)
//handle event
break;
case <- w.quit:
return
}
}
}
ch := make(chan Event, 100)
quit := make(chan bool, 0)
// Start workers
for i:=0; i<10; i++{
worker := &Worker{i,ch,quit}
go worker.start()
}
//
func addEvent (e Event) {
ch <- e
}
当你完成后,只需要close(quit)
杀死所有工人。
编辑:来自以下评论:
在这种情况下主循环是什么样的?
取决于。如果您有固定数量的事件,则可以使用WaitGroup,如下所示:
type Worker struct {
id int
ch chan Event
quit chan bool
wg *sync.WaitGroup
}
func (w *Worker) start {
for {
select {
case e := <- w.ch:
//handle event
wg.Done()
break;
case <- w.quit:
return
}
}
}
func main() {
ch := make(chan Event, 100)
quit := make(chan bool, 0)
numberOfEvents := 100
wg := &sync.WaitGroup{}
wg.Add(numberOfEvents)
// start workers
for i:=0; i<10; i++{
worker := &Worker{i,ch,quit,wg}
go worker.start()
}
wg.Wait() // Blocks until all events are handled
}
如果事先不知道事件的数量,您可以阻止退出频道:
<- quit
一旦另一个goroutine关闭了频道,你的节目也会终止。
答案 1 :(得分:1)
要使事情更加异步,你可以
为活动频道添加容量
evtCh := make(chan Event, 10)
异步调用handleEvent(e)
go handleEvent(e)
在处理程序中异步添加事件
go addEvent(e)
或者,如果您希望以确定的顺序处理事件,则可以直接在处理程序中调用handleEvent(e)
而不是addEvent(e)