为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
九州编程 2022-11-28 17:15:08
考虑一组检查工作,每个检查工作都有独立的逻辑,因此它们似乎可以并发运行,例如:type Work struct {    // ...}// This Check could be quite time-consumingfunc (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?
查看完整描述

3 回答

?
哔哔one

TA贡献1854条经验 获得超8个赞

取消(阻塞)发送

您最初的问题询问如何取消发送操作。频道上的发送基本上是“即时的”。如果通道的缓冲区已满并且没有准备好的接收器,则通道上的发送会阻塞。


您可以使用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-consuming

func (w *Work) Check(ctx context.Context, workDuration time.Duration) bool {

    // Do your thing and monitor the context!


    select {

    case <-ctx.Done():

        return false

    case <-time.After(workDuration): // Simulate work

        return true

    case <-time.After(2500 * time.Millisecond): // Simulate failure after 2.5 sec

        return false

    }

}

CheckAll()可能看起来像这样:


func CheckAll(works []*Work) {

    ctx, cancel := context.WithCancel(context.Background())

    defer cancel()


    num := len(works)

    results := make(chan bool, num)


    wg := &sync.WaitGroup{}

    for i, w := range works {

        workDuration := time.Second * time.Duration(i)

        wg.Add(1)

        go func(w *Work) {

            defer wg.Done()

            result := w.Check(ctx, workDuration)

            // You may check and return if context is cancelled

            // so result is surely not sent, I omitted it here.

            select {

            case results <- result:

            case <-ctx.Done():

                return

            }

        }(w)

    }


    go func() {

        wg.Wait()

        close(results) // This allows the for range over results to terminate

    }()


    for result := range results {

        fmt.Println("Result:", result)

        if !result {

            cancel()

            break

        }

    }

}

测试它:


CheckAll(make([]*Work, 10))

输出(在Go Playground上尝试):


Result: true

Result: true

Result: true

Result: false

我们true打印了 3 次(工作在 2.5 秒内完成),然后故障模拟开始,返回false并终止所有其他工作。


请注意,sync.WaitGroup上面示例中的 并不是严格需要的,因为results它有一个能够保存所有结果的缓冲区,但总的来说它仍然是一个很好的做法(如果您将来使用较小的缓冲区)。


查看完整回答
反对 回复 2022-11-28
?
料青山看我应如是

TA贡献1772条经验 获得超8个赞

简短的回答是:

return除非 goroutine 本身到达其堆栈的末尾,否则您不能取消或关闭任何 goroutine 。

如果你想取消某些东西,最好的方法是将 a 传递给它们并在例程中context.Context收听它。context.Done()每当上下文被取消时,你应该return在执行 defers(如果有的话)后 goroutine 会自动死掉。


查看完整回答
反对 回复 2022-11-28
?
凤凰求蛊

TA贡献1825条经验 获得超4个赞

package main


import "fmt"


type Work struct {

    // ...

    Name string

    IsSuccess chan bool

}


// This Check could be quite time-consuming

func (w *Work) Check() {

    // return succeed or not


    //...

    if len(w.Name) > 0 {

        w.IsSuccess <- true

    }else{

        w.IsSuccess <- false

    }


}



//堆排序

func main() {

    works := make([]*Work,3)

    works[0] = &Work{

        Name: "",

        IsSuccess: make(chan bool),

    }

    works[1] =  &Work{

        Name: "111",

        IsSuccess: make(chan bool),

    }

    works[2] =&Work{

        Name: "",

        IsSuccess: make(chan bool),

    }


    for _,w := range works {

        go w.Check()

    }


    for i,w := range works{

        select {

        case checkResult := <-w.IsSuccess :

            fmt.Printf("index %d checkresult %t \n",i,checkResult)

        }

    }

}

在此处输入图像描述


查看完整回答
反对 回复 2022-11-28
  • 3 回答
  • 0 关注
  • 109 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信