4345


一般には [`sync.WaitGroup`](http://golang.org/pkg/sync/#WaitGroup)
で実現される並行処理の完了待機を、チャネル( [`chan`](http://www.golang-
book.com/10/index.htm#section2) )だけをつかって記述することはできますか？

[code]

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
      wg.Add(1)
      go func(i int) {
        // 並行処理[i]
        wg.Done()
      }(i)
    }
    wg.Wait()
    
[/code]


CC BY-SA 3.0
2015-01-09T16:07:13.823
4343
2015-01-10T07:33:43.397
2015-01-09T17:03:51.233
4978
49
question
go
sync.WaitGroup 相当をチャネルのみで記述可能？

[code]

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        done := make(chan bool)
        for i := 0; i < 10; i++ {
            go func(i int) {
                time.Sleep(time.Second * time.Duration(i))
                fmt.Println("Finish:", i)
                done <- true
            }(i)
        }
    
        for i := 0; i < 10; i++ {
            <-done
        }
        close(done)
        fmt.Println("All goroutines are done.")
    }
    
[/code]

なお、別解として以下の様にした場合、

[code]

    func main() {
        done := make(chan int)
        for i := 0; i < 10; i++ {
            go func(i int) {
                time.Sleep(time.Second * time.Duration(i))
                done <- i
            }(i)
        }
    
        for i := range done {
            fmt.Println(i)
        }
    
[/code]

~~`for loop` が終了した時点で以下の様なエラーが発生してしまいました。~~  
以下のエラーメッセージを出力して異常終了します。

[code]

    fatal error: all goroutines are asleep - deadlock!
    
[/code]

これは、`for ... range done` で 10 個の goroutine から値を取り出した後、さらに channel
からデータを読み込もうとしたためです。


CC BY-SA 3.0
2015-01-09T16:47:57.863
4345
2015-01-10T01:29:02.403
2015-01-10T01:29:02.403
4343
answer


ぽいものできましたが、そういう質問じゃなかったと気づきました・・・

こういうのは channel の典型的な使い方の一つですか ~~ら簡単ですね~~ 。

<https://play.golang.org/p/BpITj7r1fA>

[code]

    package main
    
    import "sync"
    import . "fmt"
    import . "time"
    
[/code]

[code]

    type Blockers struct {
      counter int
      mx      sync.Mutex
      waiting bool
      ch      chan bool
    }
    
    func NewBlockers() (blockers *Blockers){
      blockers         = &Blockers{}
      blockers.ch      = make(chan bool)
      blockers.mx      = sync.Mutex{}
      blockers.waiting = false
      return blockers
    }
    
    func (r *Blockers) Add( additive int) {
      r.mx.Lock()
      r.counter += additive
      r.mx.Unlock()
    }
    
    func (r *Blockers) Wait() {
      r.mx.Lock()
      if ( r.counter == 0) {
        r.mx.Unlock()
        return;
      }
      if r.waiting {
        // Wait() 過剰
        panic("\"Wait\"ing already")
      }
      r.waiting = true
      r.mx.Unlock()
    
      <- r.ch
      r.waiting = false
    }
    
    func (r *Blockers) Done() {
      r.mx.Lock()
      defer r.mx.Unlock()
    
      if  r.counter == 0 {
        // Done() 過剰
        return
      }
      r.counter--
      if r.counter == 0 && r.waiting {
        r.ch <- true
      }
    }
    
[/code]

[code]

    func main() {
      blockers := NewBlockers()
    
      for i := 0; i < 10; i++ {
        blockers.Add(1)
        go func(i int) {
          Sleep(Millisecond * 50 * Duration(i))
          Println("Finished:", i)
          blockers.Done()
        }(i)
      }
    
      blockers.Wait()
    }
    
[/code]

**改訂:** `sync.Mutex` を使うようにしています  
**改訂:** チャンネル1回の入出力のみとし、エラーハンドリングなどのコードをちょっと追加


CC BY-SA 3.0
2015-01-09T19:53:00.003
4348
2015-01-10T06:11:15.063
2015-01-10T06:11:15.063
4978
4978
4343
answer


ちょっと違うアプローチで。。  
goroutineの分だけchannel作ると言う手も。

[code]

    chs := make([]chan struct{}, 10)
    for i := 0; i < 10; i++ {
        chs[i] = make(chan struct{})
        go func(i int, ch chan struct{}) {
            // 並行処理[i]
            ch <- struct{}{}
        }(i, chs[i])
    }
    
    // wait
    for _, ch := range chs {
        <-ch
    }
    
[/code]

ちょっとコスト高かもですが、goroutineからerrorや戻り値受け取れるというメリットも。


CC BY-SA 3.0
2015-01-10T04:28:31.153
4358
2015-01-10T07:33:43.397
2015-01-10T07:33:43.397
7282
7282
4343
answer