应该在关闭时排空缓冲通道

时间:2016-03-06 13:29:37

标签: go channel

在Go

中给出(部分)填充的缓冲通道
ch := make(chan *MassiveStruct, n)
for i := 0; i < n; i++ {
    ch <- NewMassiveStruct()
}

建议在关闭它时(由作者)将频道耗尽,以防读者何时从中读取信息(例如,那些频道数量有限并且他们目前正忙)?那是

close(ch)
for range ch {}

如果频道上还有其他并发读者,这样的循环是否可以保证结束?

上下文:具有固定数量的工作线程的队列服务,它应该在服务停止时丢弃处理排队的任何内容(但不一定在之后进行GC操作)。所以我结束时向工人表明服务正在终止。我可以排空剩余的&#34;队列&#34;立即让GC释放分配的资源,我可以读取并忽略工作者中的值,我可以离开通道,因为在读取器中运行并在编写器中将通道设置为nil,以便GC清理所有内容。我不确定哪种方式最干净。

3 个答案:

答案 0 :(得分:5)

有更好的方法来实现您想要实现的目标。您当前的方法可能会导致丢失一些记录,并随机处理其他记录(因为排水循环正在竞争所有消费者)。这并没有真正实现目标。

你想要的是取消。以下是Go Concurrency Patterns: Pipelines and cancellation

的示例
func sq(done <-chan struct{}, in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            select {
            case out <- n * n:
            case <-done:
                return
            }
        }
    }()
    return out
}

您将done频道传递给所有goroutines,并在您希望它们全部停止处理时将其关闭。如果你这么做了,你可能会发现golang.org/x/net/context包很有用,这使得这种模式正式化,并增加了一些额外的功能(比如超时)。

答案 1 :(得分:4)

这取决于你的程序,但一般来说我倾向于拒绝(你不需要在关闭之前清除频道):如果关闭它时你的频道中有项目,任何读者仍然从频道阅读将收到这些项目,直到该频道为空。

以下是一个例子:

package main

import (
    "sync"
    "time"
)

func main() {

    var ch = make(chan int, 5)
    var wg sync.WaitGroup
    wg.Add(1)

    for range make([]struct{}, 2) {
        go func() {
            for i := range ch {
                wg.Wait()
                println(i)
            }
        }()
    }

    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)

    wg.Done()
    time.Sleep(1 * time.Second)
}

在这里,程序将输出所有项目,尽管在任何读者甚至可以从频道读取之前严格关闭频道。

答案 2 :(得分:1)

我觉得所提供的答案实际上并没有明确说明既不需要排水也不需要关闭。因此,对于我所描述的上下文的以下解决方案看起来很干净,它终止了工作者并删除了对它们或所讨论的通道的所有引用,因此,让GC清理通道及其内容:

type worker struct {
    submitted chan Task
    stop      chan bool
    p         *Processor
}

// executed in a goroutine
func (w *worker) run() {
    for {
        select {
        case task := <-w.submitted:
            if err := task.Execute(w.p); err != nil {
                logger.Error(err.Error())
            }
        case <-w.stop:
            logger.Warn("Worker stopped")
            return
        }
    }
}

func (p *Processor) Stop() {
    if atomic.CompareAndSwapInt32(&p.status, running, stopped) {
        for _, w := range p.workers {
            w.stop <- true
        }
        // GC all workers as soon as goroutines stop
        p.workers = nil
        // GC all published data when workers terminate
        p.submitted = nil
        // no need to do the following above:
        // close(p.submitted)
        // for range p.submitted {}
    }
}