【问题标题】:Stop for loop by passing empty struct down channel Go通过将空结构传递到通道 Go 来停止循环
【发布时间】:2020-04-03 14:56:33
【问题描述】:

我正在尝试在 Go 中创建一个轮询器,它会启动并每 24 小时执行一次函数。

我还希望能够停止轮询,我试图通过设置一个 done 通道并传递一个空结构来停止 for 循环来做到这一点。

在我的测试中,for 只是无限循环,我似乎无法停止它,我是否错误地使用了 done 通道?股票行情按预期工作。

Poller struct {
    HandlerFunc HandlerFunc
    interval    *time.Ticker
    done        chan struct{}
}

func (p *Poller) Start() error {
    for {
        select {
        case <-p.interval.C:
            err := p.HandlerFunc()
            if err != nil {
                return err
            }
        case <-p.done:
            return nil
        }
    }
}

func (p *Poller) Stop() {
    p.done <- struct{}{}
}

这是执行代码并导致无限循环的测试。

poller := poller.NewPoller(
    testHandlerFunc,
    time.NewTicker(1*time.Millisecond),
)

err := poller.Start()
assert.Error(t, err)
poller.Stop()

【问题讨论】:

  • 你能分享你执行这个的代码吗?
  • 显示的源代码看起来不错,问题可能出在代码的其他地方。
  • 您几乎总是想close 取消频道。如果接收到&lt;-p.done,您的 for 循环将返回,因此它必须在其他地方被阻止。
  • 添加测试代码,谢谢。
  • @pocockn err := poller.Start() 被阻止。所以poller.Stop() 永远不会被调用。

标签: go goroutine poller


【解决方案1】:

似乎问题出在您的用例中,您以阻塞方式调用poller.Start(),因此永远不会调用poller.Stop()。在go 项目中,在Start/Run 方法中调用goroutine 是很常见的,所以,在poller.Start() 中,我会这样做:

func (p *Poller) Start() <-chan error {
    errc := make(chan error, 1 )

    go func() {
        defer close(errc)

        for {
            select {
            case <-p.interval.C:
                err := p.HandlerFunc()
                if err != nil {
                    errc <- err
                    return
                }
            case <-p.done:
                return
            }
        }
    }

    return errc
}

此外,无需将空的struct 发送到完成频道。像close(p.done)这样的关闭频道更多的是idiomatic for go

【讨论】:

  • 我无法从 go func() {} 中返回错误。使用 go func() error {} 是惯用的吗?还是有更好的方法来处理来自 p.HandlerFunc() 的错误?
  • 最好使用单独的通道来处理错误。查看更新的示例
【解决方案2】:

Go 中没有明确的方式来广播事件以执行诸如取消之类的例程。相反,它习惯于创建一个通道,该通道在关闭时表示一条消息,例如取消它必须做的任何工作。类似这样的模式是可行的:

var done = make(chan struct{})

func cancelled() bool {
    select {
    case <-done:
        return true
    default:
        return false
    }
}     

Go-routines 可以调用 cancelled 来轮询取消。

然后您的主循环可以响应此类事件,但请确保您排空任何可能导致 go-routines 阻塞的通道。

for {
    select {
    case <-done:
    // Drain whatever channels you need to.
        for range someChannel { }
        return
    //.. Other cases
   }
}

【讨论】:

    猜你喜欢
    • 2020-05-05
    • 2016-04-11
    • 2018-06-17
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-09
    • 1970-01-01
    相关资源
    最近更新 更多