2016-04-04 32 views
5

的方式,我有一些問題,用下面的代碼:建議使用sync.WaitGroup與外部功能

package main 

import (
"fmt" 
"sync" 
) 
// This program should go to 11, but sometimes it only prints 1 to 10. 
func main() { 
    ch := make(chan int) 
    var wg sync.WaitGroup 
    wg.Add(2) 
    go Print(ch, wg) // 
    go func(){ 

     for i := 1; i <= 11; i++ { 
      ch <- i 
     } 

     close(ch) 
     defer wg.Done() 


    }() 

    wg.Wait() //deadlock here 
} 

// Print prints all numbers sent on the channel. 
// The function returns when the channel is closed. 
func Print(ch <-chan int, wg sync.WaitGroup) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    } 
    defer wg.Done() 
} 

我得到在指定的地點死鎖。我試圖設置wg.Add(1)而不是2,它解決了我的問題。我的觀點是,我沒有成功地將頻道作爲參數發送給Printer函數。有沒有辦法做到這一點?否則,我的問題的解決方案替換與go Print(ch, wg)行:

go func() { 
Print(ch) 
defer wg.Done() 
} 

和改變Printer功能:

func Print(ch <-chan int) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    } 

} 

什麼是最好的解決辦法嗎?

回答

10

嗯,首先你實際的錯誤是,你給Print方法sync.WaitGroup的副本,所以它不呼籲一個Done()方法你Wait()荷蘭國際集團上。

試試這個:

package main 

import (
    "fmt" 
    "sync" 
) 

func main() {  
    ch := make(chan int) 

    var wg sync.WaitGroup 
    wg.Add(2)  

    go Print(ch, &wg) 

    go func() { 
     for i := 1; i <= 11; i++ { 
      ch <- i 
     } 
     close(ch) 
     defer wg.Done() 
    }()   

    wg.Wait() //deadlock here 
}     

func Print(ch <-chan int, wg *sync.WaitGroup) { 
    for n := range ch { // reads from channel until it's closed 
     fmt.Println(n) 
    }    
    defer wg.Done() 
} 

現在,改變你的Print方法來刪除它的WaitGroup是一個總體上是好的想法:該方法並不需要知道的東西正在等待它完成其工作。

+1

明白了,我不知道你所需要的地址是發送給'Print'而不是'WaitGroup'本身。 我同意該方法不需要知道'WaitGroup',但假設我想要這個。那麼,明星呢?它是否挑出了我在'main'中定義的ACTUAL'WaitGrooup'?爲什麼這個不是副本? – Sahand

+0

'* sync.WaitGroup'告訴編譯器你想要一個指向WaitGroup而不是WaitGroup的指針。所以編譯器希望你通過給它一個地址來調用這個方法,所以'&wg'。 – Elwinar

+2

如果您只等待發件人完成,請勿認爲您可以刪除打印的等待組作爲主的wg.Wait可能在打印上一個值之前遇到。另外,沒有理由以延遲wg.Done()結束函數,延遲基本上意味着,最後運行這個函數。放棄推遲 –

1

我同意@Elwinar的解決方案,即通過將Waitgroup的副本傳遞給Print函數導致代碼中的主要問題。

這意味着wg.Done()main中定義的wg副本上運行。因此,main中的wg無法減少,因此當wg.Wait()爲主時會發生死鎖。

既然你也問最好的做法,我可以給你我自己的一些建議:

  • 不要在Print刪除defer wg.Done()。由於您主要的goroutine是發送者,打印是接收者,因此在接收者例程中刪除wg.Done()將導致未完成的接收者。這是因爲只有您的發件人與您的主人同步,所以在您的發件人完成後,您的主人已完成,但接收人仍有可能工作。我的觀點是:在你的主程序完成後,不要留下一些懸空的公用事件。關閉它們或等待它們。

  • 記得要到處都有恐慌症,特別是匿名goroutine。我看到很多golang程序員忘記了在goroutines中進行恐慌恢復,即使他們記得恢復正常功能。當你希望你的代碼行爲正確時,或者至少在發生意外事件時優雅的時候,這很關鍵。

  • 使用defer每前重要的呼叫,像sync相關的調用,之初,因爲你不知道在哪裏的代碼可能會斷裂。假設您在wg.Done()之前刪除defer,並且在您的示例中發生了匿名goroutine中的恐慌。如果你沒有恐慌的恢復,它會驚慌失措。但是如果你有恐慌症,康復會發生什麼?現在一切都好?不,你會因爲恐慌而跳過你的wg.Done()而在wg.Wait()處遇到死鎖!然而,即使發生恐慌,通過使用defer,這個wg.Done()也會在最後執行。另外,close之前的延期也很重要,因爲它的結果也影響了溝通。

因此,這裏是根據我上面提到的幾點修改了代碼:

package main 

import (
    "fmt" 
    "sync" 
) 

func main() { 
    ch := make(chan int) 
    var wg sync.WaitGroup 
    wg.Add(2) 
    go Print(ch, &wg) 
    go func() { 

     defer func() { 
      if r := recover(); r != nil { 
       println("panic:" + r.(string)) 
      } 
     }() 

     defer func() { 
      wg.Done() 
     }() 

     for i := 1; i <= 11; i++ { 
      ch <- i 

      if i == 7 { 
       panic("ahaha") 
      } 
     } 

     println("sender done") 
     close(ch) 
    }() 

    wg.Wait() 
} 

func Print(ch <-chan int, wg *sync.WaitGroup) { 
    defer func() { 
     if r := recover(); r != nil { 
      println("panic:" + r.(string)) 
     } 
    }() 

    defer wg.Done() 

    for n := range ch { 
     fmt.Println(n) 
    } 
    println("print done") 
} 

希望它能幫助:)

+0

非常感謝兄弟! – Sahand

+0

我完全不同意_不要從Print_中刪除wg。但是,也許我的提議沒有被正確理解,我建議做的是這樣的:http://play.golang.org/p/lOopY0bYHT 這樣,你的例程不知道它是同步的,這使其更簡單。作爲一個經驗法則,*除非同步是算法的一部分,否則您的代碼應該永遠不會意識到它*。意思是,將某些東西打印到屏幕上的方法永遠不需要任何同步意識。 – Elwinar

+0

從恐慌中恢復是一個好主意,但在這個例子中它是過度的。唯一可能會恐慌的是發送到一個封閉的頻道,而你是關閉它的人。不要陷入每次檢查所有內容的陷阱中,這會讓您的代碼膨脹,導致不必要的檢查,並且使其難以閱讀。 – Elwinar