事件处理中的死锁

时间:2016-05-02 12:22:16

标签: go goroutine

所以我有一个用于事件处理的频道,主要服务器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循环中)。

我怎样才能优雅地处理这种情况?一个由切片建模的队列是个好主意吗?

2 个答案:

答案 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)