0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go 言語仕様7(goroutine と channel)

Last updated at Posted at 2021-02-20

概要

Go 言語の仕様まとめ。

前回:
Go 言語仕様6(goroutine 並行処理)

内容

  • goroutine と channel
    • channelの基本動作
    • goroutine + channel
    • close
    • select

goroutine と channel

  • channelの基本操作
宣言
// 変数名 + chan + データ型
var ch chan int
// make関数でchannelとしての機能をもたせる(送受信どちらも可能)
ch = make(chan int)
// 宣言と同時にchannelとしての機能をもたせる(送受信どちらも可能)
ch2 := make(chan int)

// 送信専用channel
var sendChan <-chan int
// 受信専用channel
var receiveChan chan<- int
buffer
// bufferサイズ(容量)を調べるには、cap関数を使う
var ch chan int
fmt.Println(cap(ch)) // 0

// make関数の第2引数でbufferサイズを設定できる
ch2 := make(chan int, 5)
fmt.Println(cap(ch2)) // 5
送受信
// 送信
ch := make(chan int, 5)
ch <- 100
// データを一つ送信したため、chのデータ数は1
fmt.Println(len(ch)) // 1
// 送信したデータを受信する
fmt.Println(<-ch) // 100
// データを受信したため、データ数は0
fmt.Println(len(ch)) // 0
// データ数0の状態でさらにデータを受信しようとするとエラー
fmt.Println(<-ch) // fatal error: all goroutines are asleep - deadlock!

// 送信したデータの受信先を変数にもできる
ch2 := make(chan int, 5)
ch2 <- 100
ch2 <- 200
ch2 <- 300
i := <-ch2
// データの受信は、送信した順になる
fmt.Println(i) // 100
i = <-ch2
fmt.Println(i) // 200

// また、bufferサイズを超えた数のデータを送信しようとするとエラーとなる
ch3 := make(chan int, 3)
ch3 <- 10
ch3 <- 20
ch3 <- 30
ch3 <- 40 // fatal error: all goroutines are asleep - deadlock!

  

  • goroutine + channel

channelは、goroutine間でデータの送受信をするために用いられる。

func goroutine(c chan string) {
	for {
		// 3. 送信されたデータを受信する
		fmt.Println(<-c)
	}
}

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	// 2. goroutineが走る
	go goroutine(ch1)
	go goroutine(ch2)

	for i := 0; i < 10; i++ {
		// 1. channelにデータ送信
		ch1 <- "Hello"
		ch2 <- "Golang"
		time.Sleep(50 * time.Millisecond)
	}
}
出力結果
Hello
Golang
Hello
Golang
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello

  

  • close

生成したchannelは、初期状態ではオープン状態だが、明示的にクローズ状態にできる。

// makeで作成したchannelはオープン状態
ch := make(chan int, 1)

// close関数の引数にchannelを渡して、明示的にクローズする
close(ch)

// クローズしたchannelに対してデータを送信するとruntime errorになる
ch <- 1 // panic: send on closed channel

クローズしたchannelに対して送信は不可だが、送信は可。

ch := make(chan int, 1)
close(ch)
// クローズしたchannelからの受信は可能
fmt.Println(<-ch) // 0

受信の場合は、2つの変数を割り当てられる。
2つ目の変数には、boolでchannelのオープン/クローズ状態を受け取れる。

ch := make(chan int, 1)
ch <- 1
i, ok := <-ch
// オープン状態のため、true
fmt.Println(i, ok) // 1 true

// channelをクローズ
close(ch)
i, ok := <-ch
// クローズしたため、false
fmt.Println(i, ok) // 0 false

// ※ 正確には、channelのbufferが空且つクローズ状態がfalseが返ってくる条件
ch2 := make(chan int, 2)
ch2 <- 2
close(ch3)
i2, ok := <-ch2
// channelはクローズしたが、データを受信していて空ではないため、trueとなる
fmt.Println(i2, ok) // 3 true

channelをrangeでループする際の注意点。
明示的にクローズしないと、受信している値をすべて取り出した後もループは続くため、エラーになる。

クローズしないと、4回目のループでdeadlockがかかるため、

func main() {
	ch := make(chan int, 3)

	ch <- 1
	ch <- 2
	ch <- 3

	for i := range ch {
		fmt.Println(i)
		// 1
		// 2
		// 3
		// fatal error: all goroutines are asleep - deadlock!
	}
}

closeしておく必要がある。

func main() {
	ch := make(chan int, 3)

	ch <- 1
	ch <- 2
	ch <- 3

	close(ch)

	for i := range ch {
		fmt.Println(i)
		// 1
		// 2
		// 3
	}
}

goroutineとの組み合わせで動作確認。
100回データの受信をループさせて、goroutineで受信したデータをchannelがクローズするまで出力してみる。

func test(c chan int, s string) {
	for {
		i, ok := <-c
		// channelがクローズしたら、break
		if !ok {
			break
		}
		fmt.Println(i, s)
	}
	// 最後にENDを出力
	fmt.Println("END")
}

func main() {
    ch1 := make(chan int, 2)

    go test(ch1, "Goloang")
    go test(ch1, "Java")
    go test(ch1, "Python")

    for i := 0; i < 100; i++ {
        // channelにデータが送信されたら、3つのgoroutineのどれかが動く
    	ch1 <- i
    }
    // ループが終わったらクローズする
    close(ch1)
    // closeした後にgoroutineの処理完了までちょっと待つ
    time.Sleep(1 * time.Second)
}

  

  • select

複数のchannelの送受信を行う際に使う構文。

selectなしで、複数のchannelを扱おうとする場合、
以下のように、どれか一つでも送受信に失敗すると後続の処理が実行されなくなってしまう。

func main() {
	ch1 := make(chan int, 3)
	ch2 := make(chan string, 3)

	// ch2のみデータを送信する
	ch2 <- "Golang"

	// ch1は空なので、エラー
	fmt.Println(<-ch1) // fatal error: all goroutines are asleep - deadlock!
	// データは受信しているが、ch1でエラーになり、処理が止まってしまう
	fmt.Println(<-ch2)
}

上記を回避するために、selectを使うことで、複数のchannelをgoroutineを停止させることなく、実行できる。

func main() {
	ch1 := make(chan int, 3)
	ch2 := make(chan string, 3)

	// ch2のみデータを送信する
	ch2 <- "Golang"

	// switchとは違い、どのcaseが実行されるかはランダム
	select {
	// ch1に受信時の処理
	case v1 := <-ch1:
		fmt.Println(v1)
	// ch2に受信時の処理
	case v2 := <-ch2:
		fmt.Println(v2)
	// caseに当てはまらない時の処理
	default:
		fmt.Println("default")
	}
}

次回

Go 言語仕様8(ポインタ)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?