2016-04-04 7 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() 
} 

は私が指定した場所でのデッドロックを取得します。私は012の代わりにwg.Add(1)を試して、それは私の問題を解決します。私の信念は、Printer関数の引数としてチャンネルを正常に送信していないということです。それを行う方法はありますか?最善の解決策は何

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

} 

:へ

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

Printer機能を変更:そうでなければ、私の問題を解決するには、とのgo Print(ch, wg)ラインを交換していますか?

答えて

10

まあ、最初にあなたの実際のエラーは、Print方法にsync.WaitGroupのコピーを与えているということですので、それはあなたが上のINGのWait()だ1上Done()メソッドを呼び出すことはありません。

代わりにこれを試してみてください:

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() 
} 

を、それのWaitGroupを削除するために、あなたのPrint方法を変更することは、一般的に良いアイデアです:メソッドは、その仕事を終了するのを何かが待っているかを知る必要はありません。

+1

がそれを手に入れた、私はあることをあなたがアドレスを必要と知らなかったお役に立てば幸いです'WaitGroup'自体の代わりに' Print'に送られました。 私はメソッドが 'WaitGroup'について知る必要はないと同意しますが、とにかくこれを必要としているとします。それでは、星は何ですか?私は 'main'で定義した' WaitGrooup'を選んでいますか?なぜこのコピーはコピーではないのですか? – Sahand

+0

'* sync.WaitGroup'は、' WaitGroup'ではなく 'WaitGroup'へのポインタをコンパイラに伝えます。したがって、コンパイラはあなたにアドレスを与えてメソッドを呼び出すことを期待しています。したがって、 '&wg'です。 – Elwinar

+2

送信者が完了するのを待っていただけで、最後の値が印刷される前にメインのwg.Waitが落ちてしまうので、プリントのウェイトグループを削除できないと思わないでください。また、defer()、defer()関数を終了する理由は、基本的には、これを最後に実行することを意味します。遅れを落とす –

1

あなたのコードの主な問題は、WaitgroupのコピーをPrintの関数に渡したことに起因する@Elwinarの解決法に同意します。

これはmainで定義したwgのコピーでwg.Done()が動作していることを意味します。したがって、mainwgは減少することができませんでした。したがって、メインにwg.Wait()があるとデッドロックが発生します。

あなたはまた、ベストプラクティスについて尋ねているので、私はあなたに私自身のいくつかの提案を与えることができる:

  • Printdefer wg.Done()を削除しないでください。 mainのgoroutineが送信者で、印刷がレシーバーであるため、レシーバールーチン内のwg.Done()を削除すると未完成のレシーバーが発生します。これはあなたの送信者だけがあなたのメインと同期しているので、あなたの送信者が完了した後、あなたのメインは完了していますが、受信機がまだ動作している可能性があります。私のポイントは:あなたのメインルーチンが終了した後、いくつかのぶら下がったゴルーチンを残さないでください。それらを閉じたり、それらを待ってください。

  • パニック回復はどこでも、特に匿名のゴルーチンを覚えておいてください。私はgolangプログラマーの多くがゴルーチンにパニックリカバリを忘れているのを見てきました。予期せぬことが起こったときに、コードが正しく動作するように、または少なくとも正常に動作させるためには、非常に重要です。初めでsync関連の呼び出しなどのすべての重要な呼び出し、前

  • 使用deferコードが壊れる可能性が場所がわからないので。例えば、wg.Done()の前にdeferを削除し、あなたの例であなたの匿名のゴルーチンにパニックが発生したとします。パニックが回復しない場合、パニックになります。しかし、パニックが回復したらどうなりますか?今はすべてうまいですか?いいえ。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

私は完全に同意していません_WgをPrint_から削除しないでください。しかし、おそらく私の提案が正しく理解されていない、私はそれのような何かをすることをお勧めします:http://play.golang.org/p/lOopY0bYHT そのように、あなたのルーチンは、それはより簡単になります。経験則として、同期がアルゴリズムの一部でない限り、あなたのコードはそれを意識するべきではありません。つまり、画面に何かを印刷する方法では、同期意識は必要ありません。 – Elwinar

+0

パニックから回復するための良いアイデアだが、この例ではそれは過ぎている。パニックになる可能性があるのは、閉じたチャネルに送信することだけです。閉じたチャネルにすることです。毎回すべてをチェックするという罠に陥ってはいけません。あなたのコードを不必要なチェックで膨らませて読みにくくします。 – Elwinar

関連する問題