2017-09-11 49 views
3

像这里一样,我创建了一个go playground示例:sGgxEh40ev,但无法使其工作。如何根据该例程的返回值来停止goroutine

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! 

这可能吗?我错在哪里

+0

您必须结束您的main for循环。正如错误告诉你:没有任何东西到达水库。 – Volker

+0

@Volker,但即使我做'break'循环的主要,然后做'退出< - 真正的',仍然僵局,你可以帮助创建一个工作场所的例子吗? – lnshi

+0

在你的程序中,'res < - idx'和'quit < - true'都等待它们的值被接收,导致死锁。一个更好的使用渠道模式可以在这里找到(https://play.golang.org/p/uf94rfXkva) –

回答

5

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

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

在工人够程,你必须res使用适当的case分支发送值,而不是在default分支:

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

,并在主够程,你必须从for循环,这样的爆发主够程可以结束,因此该程序可以结束,以及:

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

较小的流量在渠道上,更好的解决方案:) – Ravi

+0

@icza ,非常感谢你的非常详细的解释,把'res < - idx'在一个案例分支上工作正常,但如果我有一些逻辑:在一些迭代中会发回一个值,但在一些迭代中没有价值需要被送回,那么如何正确处理? – lnshi

+0

@lnshi什么是“迭代”?迭代应该是什么时候需要发回,这将导致不需要发送任何东西的迭代。你应该以这种方式构建你的循环。请展示更具体的东西,也许是一个新问题。 – icza

0

最根本的问题是,如果消费者(你的情况下主)已决定退出阅读(在你的代码中这是可选的),生产者必须在发送值之间签入总是。发生了什么事情甚至在发送(和接收)退出的值之前,生产者继续并发送消费者永远无法读取的res上的下一个值 - 消费者实际上试图在退出通道上发送值期待制片人阅读。添加了一条调试语句,可以帮助您理解:https://play.golang.org/p/mP_4VYrkZZ, - 生产者试图发送res和阻止数据,然后,然后使用者试图发送退出和阻止值。僵局!

一个可能的解决方案如下(使用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

+0

哈哈,谢谢你,不错,像这样,那么我需要明确退出goroutine无条件 – lnshi

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 
} 

这种方法的潜在风险?

+0

如果你有数据竞赛,不,这不好。阅读[安全地读取一个函数指针同时没有锁?](https://stackoverflow.com/questions/41406501/is-it-safe-to-read-a-function-pointer-concurrently-without-a -lock/41407827#41407827)此外,您正在滥用为特殊情况创建的恐慌恢复,因此我不建议您这样做。 – icza

+0

@icza我做了这个游乐场[TYo_zMaEAU](https://play.golang.org/p/TYo_zMaEAU)来解释我正在用这种方法做什么,想要听到你的洞察力,如果在这种情况下你认为仍然存在潜在的风险,非常感谢你 – lnshi

+0

你读过我链接的答案吗?谈论包含数据竞争的应用程序的正确性或有用性毫无意义。期。它可能会为你正确运行十万次,然后在下一次运行中崩溃。这是不可预测的。 – icza