2017-05-25 8 views
1

concurrent.go:sync.WaitGroupを使用せずにデッドロックを防ぐ方法は?

package main 

import (
    "fmt" 
    "sync" 
) 

// JOBS represents the number of jobs workers do 
const JOBS = 2 

// WORKERS represents the number of workers 
const WORKERS = 5 

func work(in <-chan int, out chan<- int, wg *sync.WaitGroup) { 
    for n := range in { 
     out <- n * n 
    } 
    wg.Done() 
} 

var wg sync.WaitGroup 

func main() { 
    in := make(chan int, JOBS) 
    out := make(chan int, JOBS) 

    for w := 1; w <= WORKERS; w++ { 
     wg.Add(1) 
     go work(in, out, &wg) 
    } 

    for j := 1; j <= JOBS; j++ { 
     in <- j 
    } 
    close(in) 

    wg.Wait() 
    close(out) 
    for r := range out { 
     fmt.Println("result:", r) 
    } 

    // This is a solution but I want to do it with `range out` 
    // and also without WaitGroups 
    // for r := 1; r <= JOBS; r++ { 
    // fmt.Println("result:", <-out) 
    // } 
} 

例goplayにhereです。

+1

あなたは最終的にチャネルを閉じずに範囲を使用することはできません、あなたは待たずにチャンネルを閉じることはできません。あなたは何を達成しようとしていますか? (WaitGroupが存在しない時がありましたが、チャネルから手動でトークンを数えましたが、それはもっとコードでは同じ概念です) – JimB

+0

標準的な方法であるミューテックスや原子カウンターは必要ありませんWaitGroupの前のgoroutinesは、チャネルを通じて返された値をカウントすることでした。 – JimB

+0

これは状況によって異なりますが、コード内にコメントアウトされた例があります。あなたはあなたが派遣した仕事の数に達するまで、結果の数を受け取ります。 – JimB

答えて

-1

これはwaitgroupなしの同期の例です。

Example in the Go playground

package main 

import (
    "fmt" 
) 

// number of jobs workers do 
const JOBS = 10 

// number of workers 
const WORKERS = 2 

func work(in <-chan int, out chan<- int, done chan<- bool) { 
    for n := range in { 
     out <- n * n 
    } 
    done <- true 
} 

func main() { 
    in := make(chan int, JOBS) 
    out := make(chan int, JOBS) 
    done := make(chan bool, WORKERS) 

    // launch workers 
    for w := 1; w <= WORKERS; w++ { 
     go work(in, out, done) 
    } 

    // give jobs to workers 
    for j := 1; j <= JOBS; j++ { 
     in <- j 
    } 
    close(in) 

    // list the results 
    go func() { 
     i := 0 
     for r := range out { 
      fmt.Println("result:", r) 

      // when all jobs completed mark as done 
      i++ 
      if i == JOBS { 
       done <- true 
      } 
     } 
    }() 

    // wait for all goroutines to keep up 
    // WORKERS + RESULT go routines 
    for i := 0; i < WORKERS + 1; i++ { 
     <- done 
    } 

    // prevent leaking chans 
    close(out) 
} 
+1

同期のためにスリープを使用することは決して良い例ではありません。 – JimB

+1

@inanc 'WaitGroup'の使用をお勧めします。真剣に、あなたはWaitGroupに対して何を持っていますか? – icza

+1

はい、並行性を持たせるには、物事を同期させる必要があります。残念ながら、同期について考える必要があります。無料で入手することはできません。ゴルーチンの実行は保証されません。つまり、カーネルが1秒間にCPU時間を与えない場合は、結果を出力する前に終了することがあります。 – JimB

3

ゴルーチンは、並行して独立して実行されます。 Spec: Go statements:

「行く」の文は、同じアドレス空間内で、制御の独立した同時スレッドとして関数呼び出しの実行を開始、またはゴルーチン

あなたはoutチャンネルから値を受け取るためにfor rangeを使用したい場合は、それがoutチャンネルのみすべてのゴルーチンがそれに送信完了したら閉じることができることを意味します。

goroutineは同期して独立して実行されるため、同期することはできません。

WaitGroupを使用することは、それを行うための1つの方法です(outを閉じる前にすべてのゴルーチンが作業を完了するのを確実にするためです)。

あなたのコメント付きコードは別の方法です。コメント付きコードは、ゴルーチンが送信しなければならないチャンネルと同じ数のチャンネルを受信します。これは、すべてのゴルーチンが値を送信する場合にのみ可能です。同期はsend文とreceive操作です。

通常チャネルからの結果を受信し、専用ゴルーチンに、非同期的に行われ、あるいは複数のゴルーチンを使用しています。そうすることで、すべての結果をバッファできるバッファでチャネルを使用する必要はありません。すべてのワーカーが仕事を終えるのを待つためにはまだ同期が必要ですが、ゴルティンのスケジューリングと実行が同時に独立しているため、これを避けることはできません。

+0

_Different angle:_ [forステートメントdoc](https://golang.org/ref/spec#For_statements)は次のように書いています:_ 'チャンネルがnilの場合、範囲式は永遠にブロックされます。'しかし、 'out 'に' nil'を指定すると、 'range out'はブロックせず、デッドロックします(' range outの前に 'out = nil')。どうして? –

+0

@inancあなたが試したコードはわかりませんが、deadlock =すべてのgoroutinesがブロックされているので、 'range out'もブロックされています。'nil'を' out'に指定して 'for range'を実行しようとすると、メインのゴルーチンは他のゴルーチンと一緒にブロックし、従業員が終了する(または終了する)デッドロックが発生します。 – icza

+0

私はそれを手に入れました。私は[ここ](https://play.golang.org/p/WUDyMa6XtV)を試しました。 'WaitGroup'なしで動作します。これは、 'main'も' range out'とは独立して実行できるので、デッドロックしませんでした。 –

関連する問題