見出し画像

Golang_ゴルーチンとチャネル #470

Golang(Go)は並行プログラミングのために設計されたプログラミング言語で、その中心にあるのがゴルーチンとチャネルという強力な機能です。それぞれについて説明し、具体的な使い方をまとめてみます。

ゴルーチンとは?

ゴルーチンは、Goでの軽量なスレッドです。ゴルーチンを使うことで、簡単に並行処理を実装することができます。ゴルーチンは、goキーワードを使って起動されます。

以下のようにすると、「go say("world")」の部分が別スレッドに切り出され、メインで走る「say("hello")」と並行処理されます。

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("world")
	say("hello")
	
}

ちなみにmain()もゴルーチン上で走っており、これをメインゴルーチンと呼びます。goキーワードを使うことで、このメインゴルーチンと並行して走る別ゴルーチンを立て、そこで関数を実行できる形です。

チャネルとは?

チャネルは、ゴルーチン間でデータをやり取りするためのメカニズムです。チャネルを使うことで、安全にデータを共有・同期できます。チャネルはmake関数を使って作成されます。

具体的には以下のように使用できます。

package main

import "fmt"

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

func main() {
	s := [][]int{
		{1, 1, 1}, 
		{-1, -1, -1},
	}
	c := make(chan int)  // ここでチャネルを作成!!!

	for _, e := range s {
		go sum(e, c)
	} 

	v, w := <-c, <-c  // チャネルから値(ここではsum)を受信!!!
	fmt.Println(v, w)
	
}

ここでsum()は並行処理されているので、引数で渡されているスライスsの要素の順番は、処理の順番とは一致しません({-1, -1, -1}が先に処理される可能性もある)。

rangeとclose

ゴルーチンを使いやすくする機能で、rangecloseがあります。rangeはforループで使用するもので、チャネルに対して使用するとチャネルがクローズするまでループし続けます。closeはチャネルの終了を明示的に伝えます。

この2つを使用するとより可読性の高く柔軟に機能実現できます。
以下はコード例です。

package main

import (
	"fmt"
)


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

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

ゴルーチンで走らせているnumSeries関数の中で、forで次々とチャネルに値を送信し、全ての値を入れ終わったらチャネルをクローズしています。

メインゴルーチンではrangeを使ってチャネルがクローズされるまで値を受信し、受信したらプリント出力するようにしています。もし「close(c)」を書き忘れてチャネルがクローズされなければ、ここでデッドロックが起きます。

カンマOKイディオム

チャネルにもカンマOKイディオムがあり、チャネルの受信操作とともにチャネルが閉じられているかどうかをチェックできます。

i, ok := <-c
  • i: 受信した値を格納します。

  • ok: 受信が成功したかどうかを示すブール値です。チャネルが閉じられている場合、okfalseになります。

先ほどのrangeで記載したコードをカンマOKイディオムで書くと以下のようになります。

package main

import (
	"fmt"
)


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

func main() {
	c := make(chan int)
	go numSeries(10, c)
	for {
		i, ok := <-c
		if !ok {
            fmt.Println("Channel closed!")
            break
		}
		fmt.Println(i)
	}
}


一先ず基本的な部分のみ整理しましたが、ゴルーチンにはこの他にも様々な機能があり、多くの処理を実現できます。その辺もまたアウトプットしたいと思います。

ここまでお読みいただきありがとうございました!

参考

この記事が気に入ったらサポートをしてみませんか?