【问题标题】:How can I stop the goroutine based on the returned value from that goroutine如何根据该 goroutine 的返回值停止 goroutine
【发布时间】:2018-02-19 06:44:46
【问题描述】:

像这里一样,我创建了一个 go playground 示例: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!

这可能吗?我哪里错了

【问题讨论】:

  • 您必须结束您的主 for 循环。正如错误告诉你的那样:什么都没有到达 res.
  • @Volker 但即使我做break 的主循环,然后做quit &lt;- true,仍然死锁,你能帮忙创建一个作品游乐场示例吗?
  • 在您的程序中,res &lt;- idxquit &lt;- true 都在等待接收它们的值,这会导致死锁。可以找到使用渠道的更好模式here
  • @John S Perayil 这是一个很好且简单的示例,但没有消费者向生产者发出退出信号。
  • @Ravi 我只是链接到一个关于如何正确退出频道的简单示例。我应该在评论中更清楚。 Reference

标签: go channel goroutine


【解决方案1】:

由于@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
}

这种方法有什么潜在风险吗?

【讨论】:

  • 如果你有数据竞争,不,这不好。阅读Is it safe to read a function pointer concurrently without a lock?此外,您正在滥用为特殊情况创建的恐慌恢复,所以我不建议这样做。
  • @icza 我做了这个操场TYo_zMaEAU 来解释我到底在用这种方法做什么,如果在这种情况下你认为仍然存在潜在风险,想听听你的见解,非常感谢你
  • 你读过我链接的答案吗?谈论包含数据竞争的应用程序的正确性或有用性是没有意义的。时期。它可能会为您正确运行十万次,然后在下一次运行时崩溃。这是不可预测的。
【解决方案2】:

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

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

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

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

在主 goroutine 中,你必须跳出 @9​​87654339@ 循环,这样主 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..

【讨论】:

  • 渠道流量少,解决方案更好:)
  • @icza,非常感谢您提供非常详细的解释,将res &lt;- idx 放在一个案例分支上适用于这种情况,但如果我有一些逻辑:在某些迭代中会发回一个值,但是在某些迭代中不需要发回任何值,那么如何正确处理呢?
  • @lnshi 什么是“迭代”?迭代应该是在需要发回某些东西的时候,这将导致不需要发回任何东西的迭代。您应该以这种方式构建循环。请展示一些更具体的东西,也许是一个新问题。
  • @icza 和Paq01v3ScM 一样,我只想返回arbitraryNums 切片中的数字,否则什么也不做,遇到第一个负值就退出。
  • @lnshi 那么这是您的解决方案:Go Playground。基本上这里的“迭代”是该切片中的后续数字,而不是增加的索引。只需确保您发送的数字触发主 goroutine 中的 quit 分支,否则您需要在循环后手动发送一个负数。
【解决方案3】:

根本问题是,如果消费者(在您的情况下为主要)决定退出阅读(在您的代码中这是可选的),则生产者必须始终在发送值之间检查。发生的事情甚至在发送(和接收)退出值之前,生产者继续发送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

【讨论】:

  • 哈哈,谢谢你,很好,像这样我需要明确无条件退出goroutine
猜你喜欢
  • 1970-01-01
  • 2011-10-12
  • 2017-07-19
  • 2018-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-14
  • 1970-01-01
相关资源
最近更新 更多