是否可以取消未完成的 goroutines?

考虑一组检查工作,每个检查工作都有独立的逻辑,因此它们似乎可以并发运行,例如:


type Work struct {

    // ...

}


// This Check could be quite time-consuming

func (w *Work) Check() bool {

    // return succeed or not


    //...

}


func CheckAll(works []*Work) {

    num := len(works)

    results := make(chan bool, num)

    for _, w := range works {

        go func(w *Work) {

            results <- w.Check()

        }(w)

    }


    for i := 0; i < num; i++ {

        if r := <-results; !r {

            ReportFailed()

            break;

        }

    }

}


func ReportFailed() {

    // ...

}

在关注 的时候results,如果逻辑是无论哪个工作失败,我们断言所有工作都完全失败,通道中剩余的值是无用的。让剩余未完成的 goroutines 继续运行并将结果发送到通道是没有意义和浪费的,尤其是在w.Check()相当耗时的情况下。理想效果类似于:


    for _, w := range works {

        if !w.Check() {

            ReportFailed()

            break;

        }

    }

这只运行必要的检查工作然后中断,但在顺序非并发场景中。


那么,是否可以取消这些未完成的goroutines,或者发送到channel?


九州编程
浏览 110回答 3
3回答

哔哔one

取消(阻塞)发送您最初的问题询问如何取消发送操作。频道上的发送基本上是“即时的”。如果通道的缓冲区已满并且没有准备好的接收器,则通道上的发送会阻塞。您可以使用select声明和cancel您关闭的频道“取消”此发送,例如:cancel := make(chan struct{})select {case ch <- value:case <- cancel:}在另一个 goroutine 上关闭cancel通道close(cancel)将使上面的选择放弃发送ch(如果它正在阻塞)。但如前所述,发送在“就绪”通道上是“即时的”,并且发送首先评估要发送的值:results <- w.Check()这首先必须运行w.Check(),一旦完成,它的返回值将在 上发送results。取消函数调用所以你真正需要的是取消w.Check()方法调用。为此,惯用的方法是传递一个context.Context可以取消的值,它w.Check()本身必须监视并“服从”这个取消请求。请参见在取消上下文时终止函数执行请注意,您的函数必须明确支持这一点。没有函数调用或 goroutines 的隐式终止,请参阅取消 Go 中的阻塞操作。所以你Check()应该看起来像这样:// This Check could be quite time-consumingfunc (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {&nbsp; &nbsp; // Do your thing and monitor the context!&nbsp; &nbsp; select {&nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; return false&nbsp; &nbsp; case <-time.After(workDuration): // Simulate work&nbsp; &nbsp; &nbsp; &nbsp; return true&nbsp; &nbsp; case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec&nbsp; &nbsp; &nbsp; &nbsp; return false&nbsp; &nbsp; }}CheckAll()可能看起来像这样:func CheckAll(works []*Work) {&nbsp; &nbsp; ctx, cancel := context.WithCancel(context.Background())&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; num := len(works)&nbsp; &nbsp; results := make(chan bool, num)&nbsp; &nbsp; wg := &sync.WaitGroup{}&nbsp; &nbsp; for i, w := range works {&nbsp; &nbsp; &nbsp; &nbsp; workDuration := time.Second * time.Duration(i)&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; go func(w *Work) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result := w.Check(ctx, workDuration)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // You may check and return if context is cancelled&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // so result is surely not sent, I omitted it here.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case results <- result:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }(w)&nbsp; &nbsp; }&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(results) // This allows the for range over results to terminate&nbsp; &nbsp; }()&nbsp; &nbsp; for result := range results {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Result:", result)&nbsp; &nbsp; &nbsp; &nbsp; if !result {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}测试它:CheckAll(make([]*Work, 10))输出(在Go Playground上尝试):Result: trueResult: trueResult: trueResult: false我们true打印了 3 次(工作在 2.5 秒内完成),然后故障模拟开始,返回false并终止所有其他工作。请注意,sync.WaitGroup上面示例中的 并不是严格需要的,因为results它有一个能够保存所有结果的缓冲区,但总的来说它仍然是一个很好的做法(如果您将来使用较小的缓冲区)。

料青山看我应如是

简短的回答是:不。return除非 goroutine 本身到达其堆栈的末尾,否则您不能取消或关闭任何 goroutine 。如果你想取消某些东西,最好的方法是将 a 传递给它们并在例程中context.Context收听它。context.Done()每当上下文被取消时,你应该return在执行 defers(如果有的话)后 goroutine 会自动死掉。

凤凰求蛊

package mainimport "fmt"type Work struct {&nbsp; &nbsp; // ...&nbsp; &nbsp; Name string&nbsp; &nbsp; IsSuccess chan bool}// This Check could be quite time-consumingfunc (w *Work) Check() {&nbsp; &nbsp; // return succeed or not&nbsp; &nbsp; //...&nbsp; &nbsp; if len(w.Name) > 0 {&nbsp; &nbsp; &nbsp; &nbsp; w.IsSuccess <- true&nbsp; &nbsp; }else{&nbsp; &nbsp; &nbsp; &nbsp; w.IsSuccess <- false&nbsp; &nbsp; }}//堆排序func main() {&nbsp; &nbsp; works := make([]*Work,3)&nbsp; &nbsp; works[0] = &Work{&nbsp; &nbsp; &nbsp; &nbsp; Name: "",&nbsp; &nbsp; &nbsp; &nbsp; IsSuccess: make(chan bool),&nbsp; &nbsp; }&nbsp; &nbsp; works[1] =&nbsp; &Work{&nbsp; &nbsp; &nbsp; &nbsp; Name: "111",&nbsp; &nbsp; &nbsp; &nbsp; IsSuccess: make(chan bool),&nbsp; &nbsp; }&nbsp; &nbsp; works[2] =&Work{&nbsp; &nbsp; &nbsp; &nbsp; Name: "",&nbsp; &nbsp; &nbsp; &nbsp; IsSuccess: make(chan bool),&nbsp; &nbsp; }&nbsp; &nbsp; for _,w := range works {&nbsp; &nbsp; &nbsp; &nbsp; go w.Check()&nbsp; &nbsp; }&nbsp; &nbsp; for i,w := range works{&nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; case checkResult := <-w.IsSuccess :&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("index %d checkresult %t \n",i,checkResult)&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}在此处输入图像描述
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go