チャネル(channel)を通して、goroutine間でデータを送受信

Goでは、複数のgoroutine間でデータをやり取りするために、チャネル(channel)という仕組みが用意されています。チャネルを利用することで、goroutine間でデータの送受信を実行できます。

チャネルを通じたデータの送受信
(受信ブロック)

package main

import "fmt"

func sum(s []int, ch chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	// チャネルにデータを送信
	ch <- sum
}

func main() {
	// チャネルの作成
	ch := make(chan int)

	s := []int{1, 1, 1}
	go sum(s, ch)
	
	// チャネルからデータを受信(受信ブロック)
	sum := <-ch
	fmt.Println(sum)
}

sum := <-ch の箇所で処理をブロッキングしているため、sync.WaitGroup を利用して処理を待機する必要はありません。

3

チャネルのバッファ
(送信ブロック)

チャネルにバッファを付けると、受信準備ができていなくても、容量分だけチャネルに送信することができます。

チャネルの容量に空きが無くなると、送信側が処理待ち状態(送信ブロック)になります。

package main

import "fmt"

func main() {
	// 容量2のチャネルを作成
	ch := make(chan string, 2)

	ch <- "aaaa"
	ch <- "bbbb"

	// 容量2なのでエラーになります。(fatal error: all goroutines are asleep - deadlock!)
	// ch <- "cccc"

	// チャネルからデータを受信
	m1 := <-ch
	fmt.Println(m1)

	m2 := <-ch
	fmt.Println(m2)

	// 容量に空きができたので、送信できます。
	ch <- "cccc"
	m3 := <-ch
	fmt.Println(m3)
}
aaaa
bbbb
cccc

送信専用・受信専用チャネル

定義のしかたによって、チャネルを送信専用・受信専用にすることができます。

chanの右側に<-演算子を記述すると送信専用になります。
chanの左側に<-演算子を記述すると受信専用になります。

package main

import (
	"fmt"
	"sync"
)

// ch 送信専用
func test1(ch chan<- string, wg *sync.WaitGroup) {
	defer wg.Done()
	ch <- "aaaa"
}

// ch 受信専用
func test2(ch <-chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	m := <-ch
	fmt.Println(m)
}

func main() {
	var wg sync.WaitGroup
	ch := make(chan string)

	wg.Add(2)
	go test1(ch, &wg)
	go test2(ch, &wg)
	wg.Wait()
}
aaaa

rangeでチャネルがcloseされるまで受信

rangeを利用して、チャネルからデータを受信する例を示します。

close()によってチャネルがクローズされるまで、データを受信し続けます。

package main

import "fmt"

func test(s [][]int, ch chan int) {
	// チャネルを閉じる
	defer close(ch)

	for _, v := range s {
		sum := 0
		for _, v2 := range v {
			sum += v2
		}
		// チャネルに送信
		ch <- sum
	}
}

func main() {
	// チャネルの作成
	ch := make(chan int)

	s := [][]int{
		{1, 1, 1},
		{2, 2, 2},
		{3, 3, 3},
	}
	go test(s, ch)

	// チャネルが閉じられるまで受信
	for i := range ch {
		fmt.Println(i)
	}
}
3
6
9

selectで複数チャネルを同時受信

selectを利用すると、複数チャネルから同時受信できます。

package main

import (
	"fmt"
	"time"
)

func test1(ch chan<- string) {
	for {
		ch <- "test1"
		time.Sleep(2 * time.Second)
	}
}

func test2(ch chan<- string) {
	for {
		ch <- "test2"
		time.Sleep(4 * time.Second)
	}
}

func test3(quit chan<- int) {
	time.Sleep(10 * time.Second)
	quit <- 0
}

func main() {
	c1 := make(chan string)
	c2 := make(chan string)
	quit := make(chan int)
	go test1(c1)
	go test2(c2)
	go test3(quit)

	cnt := 0
	for {
		select {
		case s1 := <-c1:
			fmt.Println(s1)
		case s2 := <-c2:
			fmt.Println(s2)
		case <-quit:
			fmt.Println("quit")
			return
		default:
			cnt = cnt + 1
			fmt.Printf("(cnt: %v)\n", cnt)
			time.Sleep(1 * time.Second)
		}
	}
}
(cnt: 1)
test1
test2
(cnt: 2)
(cnt: 3)
test1
(cnt: 4)
(cnt: 5)
(cnt: 6)
test1
test2
(cnt: 7)
(cnt: 8)
(cnt: 9)
test1
(cnt: 10)
test2
quit

参考