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。相比之下,上下文解决方案很简单,不能阻塞、恐慌或其他任何事情。你可以在谷歌上搜索,找到很多博客帖子或答案告诉你同样的事情。