通过向下通道 Go 传递空结构来停止循环

Stop for loop by passing empty struct down channel Go

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

我也希望能够停止轮询,我试图通过完成通道并传递一个空结构来停止 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()

似乎问题出在您的用例中,您以阻塞方式调用了 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 中没有明确的方法来广播事件以执行取消之类的例程。相反,它惯用地创建一个通道,当关闭时表示一条消息,例如取消它必须做的任何工作。类似这样的模式是可行的:

var done = make(chan struct{})

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

Go 例程可以调用取消以轮询取消。

然后你的主循环可以响应这样的事件,但要确保你排空任何可能导致 go-routines 阻塞的通道。

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