如何根据goroutine返回的值停止goroutine

时间:2017-09-11 05:24:18

标签: go channel goroutine

就像在这里我创建了一个游乐场样本:sGgxEh40ev,但无法让它运作。

quit := make(chan bool)
res := make(chan int)

go func() {
    idx := 0
    for {
        select {
        case <-quit:
            fmt.Println("Detected quit signal!")
            return
        default:
            fmt.Println("goroutine is doing stuff..")
            res <- idx
            idx++
        }
    }

}()

for r := range res {
    if r == 6 {
        quit <- true
    }
    fmt.Println("I received: ", r)
}

输出:

goroutine is doing stuff..
goroutine is doing stuff..
I received:  0
I received:  1
goroutine is doing stuff..
goroutine is doing stuff..
I received:  2
I received:  3
goroutine is doing stuff..
goroutine is doing stuff..
I received:  4
I received:  5
goroutine is doing stuff..
goroutine is doing stuff..
fatal error: all goroutines are asleep - deadlock!

这可能吗?我哪里错了

3 个答案:

答案 0 :(得分:5)

问题是在goroutine中你使用select检查它是否应该中止,但你使用default分支来做其他工作。

如果没有通信(在default分支中列出)可以继续,则执行case分支。因此,在每次迭代中都会检查quit通道,但如果无法接收(无需退出),则执行default分支,无条件地尝试发送res上的值。现在如果主goroutine还没有准备好接收它,那将是一个僵局。这正是发送值为6时发生的情况,因为主goroutine尝试在quit上发送一个值,但是如果工作者goroutine在default分支中试图发送res,然后两个goroutines尝试发送一个值,没有人试图接收!两个通道都是无缓冲的,所以这是一个死锁。

在worker goroutine中,您必须使用正确的res分支在case上发送值,而不是default分支:

select {
case <-quit:
    fmt.Println("Detected quit signal!")
    return
case res <- idx:
    fmt.Println("goroutine is doing stuff..")
    idx++
}

在主要的goroutine中,你必须从for循环中突破,这样主要的goroutine才能结束,所以程序也可以结束:

if r == 6 {
    quit <- true
    break
}

这一次输出(在Go Playground上尝试):

goroutine is doing stuff..
I received:  0
I received:  1
goroutine is doing stuff..
goroutine is doing stuff..
I received:  2
I received:  3
goroutine is doing stuff..
goroutine is doing stuff..
I received:  4
I received:  5
goroutine is doing stuff..
goroutine is doing stuff..

答案 1 :(得分:0)

根本问题是,如果消费者(在您的情况下是主要的)决定退出阅读(在您的代码中这是可选的),生产者必须始终在发送值之间签入。甚至在发送(和接收)quit的值之前发生了什么,生产者继续前进并发送消费者永远无法读取的res上的下一个值 - 消费者实际上正在尝试在退出通道上发送期望生产者读取的值。添加了一个可以帮助您理解的调试语句:https://play.golang.org/p/mP_4VYrkZZ, - 生产者正试图在res和阻塞上发送7,然后消费者尝试在退出和阻止时发送值。死锁!

一种可能的解决方案如下(使用Waitgroup是可选的,只有在返回之前需要从生产者端清除退出时才需要):

package main

import (
    "fmt"
    "sync"
)

func main() {
    //WaitGroup is needed only if need a clean exit for producer
    //that is the producer should have exited before consumer (main)
    //exits - the code works even without the WaitGroup
    var wg sync.WaitGroup
    quit := make(chan bool)
    res := make(chan int)

    go func() {
        idx := 0
        for {

            fmt.Println("goroutine is doing stuff..", idx)
            res <- idx
            idx++
            if <-quit {
                fmt.Println("Producer quitting..")
                wg.Done()
                return
            }

            //select {
            //case <-quit:
            //fmt.Println("Detected quit signal!")
            //time.Sleep(1000 * time.Millisecond)
            //  return
            //default:
            //fmt.Println("goroutine is doing stuff..", idx)
            //res <- idx
            //idx++
            //}
        }


    }()
    wg.Add(1)
    for r := range res {
        if r == 6 {
            fmt.Println("Consumer exit condition met: ", r)
            quit <- true
            break
        }
        quit <- false
        fmt.Println("I received: ", r)
    }
    wg.Wait()

}

输出:

goroutine is doing stuff.. 0
I received:  0
goroutine is doing stuff.. 1
I received:  1
goroutine is doing stuff.. 2
I received:  2
goroutine is doing stuff.. 3
I received:  3
goroutine is doing stuff.. 4
I received:  4
goroutine is doing stuff.. 5
I received:  5
goroutine is doing stuff.. 6
Consumer exit condition met:  6
Producer quitting..

在操场上:https://play.golang.org/p/N8WSPvnqqM

答案 2 :(得分:0)

由于@ icza的答案非常简洁,@ Ravi将采用同步方式。

但是因为我不想花费那么多精力来重构代码,而且我也不想去同步的方式,所以最终转到defer panic recover流量控制,如下所示:

func test(ch chan<- int, data []byte) {
    defer func() {
        recover()
    }()
    defer close(ch)

    // do your logic as normal ...
    // send back your res as normal `ch <- res`
}

// Then in the caller goroutine

ch := make(chan int)
data := []byte{1, 2, 3}
go test(ch, data)

for res := range ch {
    // When you want to terminate the test goroutine:
    //     deliberately close the channel
    //
    // `go -race` will report potential race condition, but it is fine
    //
    // then test goroutine will be panic due to try sending on the closed channel,
    //     then recover, then quit, perfect :) 
    close(ch)
    break
}

这种方法有任何潜在的风险吗?