2017-09-11 54 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