Go 如何中断通道上的发送
我正在尝试实现一个组件,该组件查询数据库中的事件,并通知用户是否有新事件可通过通道访问。基本上是这样的:Go 如何中断通道上的发送,go,Go,我正在尝试实现一个组件,该组件查询数据库中的事件,并通知用户是否有新事件可通过通道访问。基本上是这样的: type struct Watcher { events chan Event } func (w *Watcher) Watch() <- chan Event { w.fetch() return w.events } func (w *Watcher) Stop() { // ??? } func (w *Watcher) fetch() { //... f
type struct Watcher {
events chan Event
}
func (w *Watcher) Watch() <- chan Event {
w.fetch()
return w.events
}
func (w *Watcher) Stop() {
// ???
}
func (w *Watcher) fetch() {
//...
for {
//...
e := fetchEvent()
w.events <- e
time.sleep(10)
//...
}
}
// sender part
events, doneChan := NewWatcher()
select {
case done := <-doneChan:
// cleanup and return
case events<- message:
}
func (w *Watcher) Stop() {
w.doneChan<- true
}
类型结构观察程序{
事件
}
func(w*Watcher)Watch()将另一个频道添加到您的Watcher
,如doneChan
,并在您的发布者(也称发送者)中使用选择
收听此频道,如下所示:
type struct Watcher {
events chan Event
}
func (w *Watcher) Watch() <- chan Event {
w.fetch()
return w.events
}
func (w *Watcher) Stop() {
// ???
}
func (w *Watcher) fetch() {
//...
for {
//...
e := fetchEvent()
w.events <- e
time.sleep(10)
//...
}
}
// sender part
events, doneChan := NewWatcher()
select {
case done := <-doneChan:
// cleanup and return
case events<- message:
}
func (w *Watcher) Stop() {
w.doneChan<- true
}
您的新结构可能类似于:
type struct Watcher {
events chan Event
doneChan chan bool
}
或者,您可以根据上下文停止。这是更好的,因为如果由于某种原因,另一侧已经停止或被其他东西阻塞,那么使用停止通道可能会阻塞,等等
类型观察程序结构{
事件
cancel context.CancelFunc
}
func(w*Watcher)Watch()我将做一些假设:Watcher
将成为您计划传播的类型,或者至少,您很高兴它的功能隐藏在干净的界面后面。虽然可能不会立即引起关注,但您希望这样的组件能够安全地同时使用
type Watcher struct {
ch chan Event
done chan struct{}
}
// New now starts fetching, leave out go w.fetch(ctx) if you don't want this to happen
func New(ctx context.Context) *Watcher {
w := &Watcher{
ch: make(chan Event, 10), // some buffer if needed
done: make(chan struct{}), // no buffer needed
}
go w.fetch(ctx)
return w
}
// If you only want to start fetching events when this is called, then uncomment the first line, and allow for context to be passed
func (w *Watcher) Watch() <-chan Event {
// go w.fetch(ctx)
return w.ch
}
func (w *Watcher) fetch(ctx context.Context) {
// this runs in a routine
// if we stop fetching, close channels
defer func() {
if w.done != nil {
close(w.done)
}
w.done = nil
close(w.ch)
}()
for {
select {
case <-ctx.Done():
return // outside context was cancelled
case <-w.done:
w.done = nil // prevent second close on done channel
return // Stop was called
default:
w.ch <- fetchEvent() // will block until channel buffer is available
}
}
}
func (w *Watcher) Stop() {
if w.done == nil { // field is set to nil once fetch routine returns
return
}
close(w.done) // rest is handled in fetch routine
}
使用这种类型非常简单:
ctx, cfunc := context.WithCancel(context.Background())
watcher := NewWatcher(ctx)
go func() {
for e := range watcher.Watch() {
// do something with the data
}
}()
time.Sleep(1 * time.Minute)
// either:
watcher.Stop()
// or simply:
cfunc()
您好,我不太明白您为什么从发件人的角度编写您的答案,因为在我的示例中,客户端代码是希望通过NewWatcher()
创建一个新的Watcher
实例的代码,最好让doneChan
类型为chan struct{}
(根据规范,空结构是0字节),与向频道写入单个值(仅当您有单个消费者时才有效)不同,您最好关闭频道(Stop()
实际上是close(w.doneChan)
)。如果将发送放入选择中,发送将不会被阻止。然后,您只需通知goroutine关闭或中止,而不是尝试以某种方式发送。@Moonlight:如果您现在因为“停止频道”与“上下文”的辩论而不确定该怎么办,很抱歉。也许这对你有帮助:我会有一个带有内部done
通道的Stop
函数以防万一,并允许用户在上下文中传递给构造函数。当应用程序上下文被取消时,或者调用者不再需要使用此特定调用者时,可以让观察者优雅地终止。如果要传入应用程序上下文,请将其放入Watch(ctx context.context)
函数中,并将context.Background()
替换为ctx
。频道不是阻止某事的好方法。@TehSphinX谢谢你的建议。Watch(ctx)
方法的第三行不应该是这样的:gow.fetch(ctx)
以便在后台发生吗?是的。您不需要关闭它,但如果需要,可以在返回之前在中选择将其关闭。请注意,关闭后,如果要在关闭后对同一实例再次调用Watch
,则必须创建一个新实例。然后它变得很棘手,因为您将在Watcher上的events字段中遇到数据竞争。我会把它加到我的答案中。@TheSphincX是的,谢谢,我慢慢开始对go频道感到舒服:)这段代码会在它开始进入NewWatcher
时改变行为,而不是进入Watch
。这是世界上最简单的修复方法,但还好,我将添加它以明确说明如果调用两次Stop
函数,这将导致panic
。这不是个好主意@TehSphinX:我知道,并发使用它也不安全,因为调用Stop
,而取消上下文可能会导致done
频道上的并发访问/关闭调用,要使此代码准备好在实际应用程序中使用,还需要做很多工作,但这正是编辑的目的。这就是为什么你不应该再使用停止频道的原因。因为我们有context.context。相比之下,上下文解决方案很简单,不能阻塞、恐慌或其他任何事情。你可以在谷歌上搜索,找到很多博客帖子或答案告诉你同样的事情。