我的代码需要向任意数量的侦听器传递消息(在下面的示例中称为HintInfo)。这些监听器将附加到Web或其他客户端,因此可能会变得不稳定 - 它们可能会冻结,超时并消失等等。我正在努力确保我有一个如何可靠地管理生活的良好模式循环就此。我想出的内容如下所示。基本概念是保持与每个听众相对应的频道地图。使用RWMutex以原子方式添加或删除监听器。使用读锁定发送到这些通道,并首先尝试定期同步通道发送。如果这不起作用,则启动一个单独的goroutine,它会阻塞,直到消息可以发送到该频道。可以想象,成千上万的消息可以快速堆叠到被阻止的客户端,比方说一会儿。我想确保这不会导致某种泄漏。这里的想法是,当消息进入时,即使其中一个或多个已满,它也会立即传递到所有通道。为了保留序列,首先尝试发送没有单独的goroutine,但是当队列填满后,消息仍然会到达,但可能不按顺序,因为一堆通道在不同的goroutine中发送,我很确定没有可预测的传送顺序 - 这对我的申请来说是可以接受的权衡。
侦听器负责确保释放它所使用的频道(通过调用ReleaseRecvChannel())。这会在通道上获得一个写锁定和调用close(),导致任何阻止写入恐慌的goroutine,他们从中恢复()并忽略。这应该确保正确清理的东西,有效地丢失了该侦听器的任何未传递消息(这很好,因为侦听器断开连接,这是逻辑行动)。
我的问题是,如果有人看到这种方法更好的方法或问题。我认为这会有效,但这是一个棘手的问题,我想知道是否有一些我不知道的事情。
type HintInfo struct { /* ... */ }
type hintFanout struct {
hintChs map[chan HintInfo]struct{}
l sync.RWMutex
}
func (hf *hintFanout) deliver(hi HintInfo) {
hf.l.RLock()
defer hf.l.RUnlock()
for ch := range hf.hintChs {
// try synchronous send
select {
case ch <- hi:
default:
// can't send right now do it in another goroutine
go func(ch chan HintInfo) {
defer func() { recover() }() // if send panics we ignore it
ch <- hi
}(ch)
}
}
}
func (hf *hintFanout) GetRecvChannel() chan HintInfo {
hf.l.Lock()
defer hf.l.Unlock()
ch := make(chan HintInfo, 4096)
hf.hintChs[ch] = struct{}{}
return ch
}
func (hf *hintFanout) ReleaseRecvChannel(ch chan HintInfo) {
hf.l.Lock()
defer hf.l.Unlock()
// since we have a write lock, there will be no deliver() calls
// in progress right now (only possibly old goroutines trying to
// send to a full channel)
delete(hf.hintChs, ch)
close(ch) // this will cause any goroutines waiting for a channel send to panic
}