ごまなつ Blog

楽しく働ける世界を目指して

go言語 並列実行入門

Go言語の並列実行について学んだメモです。

func goroutine(s string, wg *sync.WaitGroup){
  defer wg.Done()
  for i := 0; i < 5; i++{
    fmt.Println(s)
  }
}
func normal (s string){
  for i := 0; i < 5; i++{
    fmt.Println(s)
  }
}
func main(){
  var wg sync.WaitGroup
  wg.Add(1)
  go goroutine("world", &wg)
  normal("hello")
  wg.Wait()
}

並列実行は、time.Sleepしないと並列実行している関数が実行される前にmain関数が終了しプログラムが終わることがある。それを防ぐためにsync.WaitGroupを使う wg.Add(1)をしたら同じ数wg.Done()を呼ばないとエラーになる

goroutineを使って並列実行するが、これらは別のプログラムなのでreturnで値を返せない。そのため、channelを使って同期させる。

func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
  }
  c <- sum
}

func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int)
  go goroutine1(s, c)
  x := <-c
  fmt.Println(x)
}

mainからスライスのsとchanのcを送って、goroutineが走る。x:=<-cの部分でgoroutineのc<-sumが終わるまで止まる。 chanを複数作ってもいいし、1つのchanを複数の関数で使ってもよい。順番はキューの順番になる。

func main(){
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    ch <-300
    fmt.Println(len(ch))
}

はエラーになる。make(chan int, 2)の2はバッファで、chの最大個数を示す。 よって、2で定義したのに3つ目を入れようとしたのでエラーになる。 取り出しているとエラーにならない。取り出すのは

s := <- ch

という記法。

 func main()){
    ch := make(chan int, 2)
    ch <- 100
    fmt.Println(len(ch))
    ch <- 200
    fmt.Println(len(ch))
    close(ch)

    for c := range ch{
        fmt.Println(c)
    }
 }

chanを取り出すときにfor range文を使いたいが、close(ch)を書かずに使うとエラーになる。バッファの個数以上取り出そうとするため。close(ch)を書いていればエラーにならない

 func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
  }
  c <- sum
}

func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int)
  go goroutine1(s, c)
  x := <-c
  fmt.Println(x)
}

これの、計算の途中過程を逐次表示するようにしたいなら、

func goruoutine1(s []int, c chan int){
  sum := 0
  for _, v := range s{
      sum += v
      c <- sum
  }
  close(c)
}
func main(){
  s := []int{1, 2, 3, 4, 5}
  c := make(chan int, len(s))
  go goroutine1(s, c)
  for i := range c{
      fmt.Println(i)
  }
}

close(c)はエラー防止。chanの数をメモリに確保したいならmake(chan int, len(s))とする。

func producer(ch chan int, i int){
    //Something
    ch <- i * 2
}

func consumer(ch chan int, wg *sync.WaitGroup){
    for i := range ch{
        func (){
            defer wg.Done()
            fmt.Println("process", i * 1000)
        }()
    }  
}
func main(){
    var wg sync.WaitGroup
    ch := make(chan int)

    //producer
    for i := 0; i < 10; i++{
        wg.Add(1)
        go producer(ch, i)
    }

    //consumer
    go consumer(ch, &wg)
    wg.Wait     //すべてのWaitGroupが終わるまで待つ
    close(ch)
}

close(ch)がこの位置にあるのは、consumerのforループがまだchanがあると思って待っているため。 それを終わらせるためにclose(ch)をする。

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    //time.tickとtime.Afterはchを返す関数。
    for {
        select {
        case <-tick: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
            fmt.Println("tick.")
        case <-boom: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る
            fmt.Println("BOOM")
            return
        default:
            fmt.Println("   .")
            time.Sleep(50 * time.Millisecond)
        }
    }
    fmt.Println("#########")
}

for select文は、複数のチャネルを受信してチャネルによって処理を変えられる。selectは、for文の中にあるからずっと回る。for文がないと止まる。終わりたいなら、for文をbreakする 最後のfmt.Printlnはこのままだと実行されない。先にmain関数が終わってしまうため。実行したいなら、

OuterLoop2
 for{
    select{
    case <- tick://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
        fmt.Println("tick.")
    case <-boom://返ってくるchを変数に入れなくてもよい。入れると時刻も出る
        fmt.Println("BOOM")
        break OuterLoop2
    default:
        fmt.Println("   .")
        time.Sleep(50*time.Millisecond)
    }
 }
    fmt.Println("#########")

のようにして、ラベルを使ってfor文の上に出る。名前は何でもいい。

package main

import (
    "fmt"
    "sync"
    "time"
)

 func main() {
    c := make(map[string]int)
    go func() {
        for i := 0; i < 10; i++ {
            c["key"] += 1
        }
    }()
    go func() {
        for i := 0; i < 10; i++ {
            c["key"] += 1
        }
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(c, c["key"])
 }

このコードだと、成功したりエラーを出したりする。両方のゴルーチンがcへの書き込みをしており、cへの書き込みが同時になるとエラーになる。よって、sync, mutexを使う。

package main

import (
    "fmt"
    "sync"
    "time"
)
type Counter struct {
    v   map[string]int
    mux sync.Mutex
}

func (c *Counter) Inc(key string) {
    c.mux.Lock()
    defer c.mux.Unlock()
    c.v[key]++
}

func (c *Counter) Value(key string) int {
    c.mux.Lock()
    defer c.mux.Unlock()
    return c.v[key]
}
func main() {
    c := Counter{v: make(map[string]int)}
    go func() {
        for i := 0; i < 10; i++ {
            //c["key"] += 1
            c.Inc("Key")
        }
    }()
    go func() {
        for i := 0; i < 10; i++ {
            //c["key"] += 1
            c.Inc("Key")
        }
    }()
    time.Sleep(1 * time.Second)
    fmt.Println(c, c.Value("Key"))
}