2
1

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 5 years have passed since last update.

Goroutineとかchannel触ってみた

2
Last updated at Posted at 2018-09-17

はじめに

どうも最近golangを勉強し始めたにわかgopherです。
goroutineとは本来実装が難しい並列処理がgoなら簡単に書けるよーってやつです。
並列処理自体はgo func(){...}と書くだけで実装できるのですが、それをちゃんと制御しようとするとそれなりにチャンネルとかその辺の理解が必要なので、その辺りを勉強してみました。
僕の記事より他の人の記事を参考した方がわかりやすいと思うのでこれぐらいに。。

実装

チャンネルなし

まずはgo構文だけを使って実装してみました。

main.go
package main

import (
	"fmt"
	"time"
)

type Task struct {
	name        string
	processTime int
}

func (t *Task) ProcessTask(c chan string) {
	fmt.Printf("Start %s\n", t.name)
	for i := 0; i < t.processTime; i++ {
		time.Sleep(1 * time.Millisecond)
	}

	fmt.Printf("End %s\n", t.name)
}

var TaskA = Task{name: "TaskA", processTime: 1000}
var TaskB = Task{name: "TaskB", processTime: 3000}
var TaskC = Task{name: "TaskC", processTime: 2000}

func Parallel() {
	go TaskA.ProcessTask()
	go TaskB.ProcessTask()
	go TaskC.ProcessTask()
}

func main() {
	Parallel()
}

で実行すると、A, B, Cの順で終わるのかなと思いきや、何も実行されないまま終わっちゃいました。。??
これはどうもmain自体もgoroutineでそれぞれのgoroutineが独立に実行され終わったかどうかは影響し合わないためのようです。

そのため別のスレッドでProcessTaskを実行してもそれが終わったかどうかに関係なくmainが終わってしまうようです。

チャンネルあり

ここで、channelというやつの登場です。これはroutineどうしで情報をやりとりするもので、ベルトコンベアのように値をひとつずつ渡していきます。

これを使うことによってその次の処理がブロックできたり、他のgoroutineと値の受け渡しができるので、並列処理を制御することができます。

送信と受信があるのですがこの辺りは以下の記事がわかりやすかったので参考にしてみるといいかもしれません。

go言語初心者が図を書きながらgoroutineやgo channelを理解する(Part1)

では、channelを実装してみます。

main.go
package main

import (
	"fmt"
	"time"
)

type Task struct {
	name        string
	processTime int
}

func (t *Task) ProcessTask(c chan string) {
	fmt.Printf("Start %s\n", t.name)
	for i := 0; i < t.processTime; i++ {
		time.Sleep(1 * time.Millisecond)
	}

	c <- "End " + t.name + "!\n" // 送信
}

var TaskA = Task{name: "TaskA", processTime: 1000}
var TaskB = Task{name: "TaskB", processTime: 3000}
var TaskC = Task{name: "TaskC", processTime: 2000}

func Parallel() {
	c := make(chan string)
	go TaskA.ProcessTask(c)
	go TaskB.ProcessTask(c)
	go TaskC.ProcessTask(c)

	// 入る順番はgoroutineが終わった順
	// channelはベルトコンベアのイメージ
	m1, m2, m3 := <-c, <-c, <-c // 受信

	// 全部の<-cを受け取るまで処理がブロックされてこの処理は実行されない
	fmt.Println(m1, m2, m3)
}

func main() {
	Parallel()
}

これを実行すると、

Start TaskC
Start TaskA
Start TaskB
End TaskA!
End TaskC!
End TaskB!

と、期待通りの出力が得られますが、"End TaskX!"は一気に表示されてしまいます。
順次表示されてほしいところですが、

m1, m2, m3 := <-c, <-c, <-c // 受信

で全ての結果を受信しているので、最後のm3を受信するまではこれ以降の処理がブロックされてしまい実行待ち状態になります。

逆にひとつずつ受信するようにすれば順次結果が出力されるようになります。

main.go
func Parallel() {
	c := make(chan string)

	// 以下だと順次<-cを受け取って表示される
	m1 := <-c
	fmt.Println(m1)
	m2 := <-c
	fmt.Println(m2)
	m3 := <-c
	fmt.Println(m3)
}

まとめ

  • goroutineはchannelを使って良い感じに制御してあげないと、そもそもmainとは無関係に動くだけで何が起こっているかを知ることすらできない
  • channelは良い感じに処理をブロックしたりしてくれる

全体のコードはこちら

まだまだgolangの知識が足りないのでお前ここ間違ってるぞ的なのがあればどしどしご指摘ください、、

参考

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?