MyDocs

# Golangのgoroutine/channelとか

goroutine

goroutine は軽量なスレッド。

go f(x, y)

と書くだけ。

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("hello")
	say("world")
}

// hello
// world
// world
// hello
// hello
// world
// hello
// world
// world
// hello

channel

channel 型は <- を用いて値の送受信を行う。

ch <- v // v をチャネル ch に送信する
v := <- ch // チャネル ch から変数を v に割り当てる

チャネルは make で作る。

ch := make(chan int)

通常、片方の準備ができるまで送受信はブロックされる。

package main

import "fmt"

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

func main() {
	s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	c := make(chan int)

	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	go sum(s, c)

	x, y := <-c, <-c
	z := <-c

	fmt.Println(x, y, z)
}

// 55 15 40
// 55 40 15
// 処理終わった順かな?

バッファ

チャネルはバッファとして使える。 make の 2 つ目の引数にバッファの長さを指定できる。

ch := make(chan int, 10)

バッファ数を超えると deadlock になる。

package main

import "fmt"

func main() {
	ch := make(chan bool, 2)
	ch <- true
	ch <- true
	v := <-ch
	ch <- true
	// ch <- true // この行を入れると deadlock

	fmt.Println(len(ch))
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(v)
	fmt.Println(len(ch))
}

// 2
// true
// true
// true
// 0

close

送信側はチャネルを close できる。 受信側はチャネルを close しているか確認できる。 受信する値はなく、かつチャネルが閉じているなら okfalse になる。

v, ok := <- ch

ループのときはチャネルを使うとチャネルが閉じるまで値を受信し続ける。

package main

import "fmt"

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for i := range c {
		fmt.Println(i)
	}
}

// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34

select

select は goroutine を複数の通信操作で待たせる。 case の準備ができるまでブロックする。複数の case が準備できている場合、 case はランダムに実行される。

package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

// 0
// 1
// 1
// 2
// 3
// 5
// 8
// 13
// 21
// 34
// quit

default

ブロックせずに送受信したいときには default を使う。

package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.Tick(500 * time.Millisecond)

	for {
		select {
		case <-tick:
			fmt.Println("tick")
		case <-boom:
			fmt.Println("boom")
			return
		default:
			fmt.Println("   .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

//    .
//    .
// tick
//    .
//    .
// tick
//    .
//    .
// tick
//    .
//    .
// tick
//    .
//    .
// boom

sync.Mutex

コンフリクトを避け、 1 度に 1 つの goroutine だけが変数にアクセスできる。 Golang の標準パッケージは,排他制御を sync.MutexLockUnlock で提供している。

package main

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

type SafeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
	c.mu.Lock()
	c.v[key]++
	c.mu.Unlock()
}

func (c *SafeCounter) Value(key string) int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.v[key]
}

func main() {
	c := SafeCounter{
		v: make(map[string]int),
	}

	for i := 0; i < 1000; i++ {
		go c.Inc("somekey")
	}
	time.Sleep(time.Second)
	fmt.Println(c.Value("somekey"))
}

// 1000