Skip to content

Instantly share code, notes, and snippets.

@catatsuy
Created December 11, 2021 12:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save catatsuy/a1b7c28761e53e6eca4878b2e6eb9ad1 to your computer and use it in GitHub Desktop.
Save catatsuy/a1b7c28761e53e6eca4878b2e6eb9ad1 to your computer and use it in GitHub Desktop.

Goで並行処理初級編(goroutineとchannel)

  • Goで並行処理する時の初級編です
  • 今回の話はGoを書いている人には簡単すぎますが、並行処理やGoの経験がない人には難しすぎると思います
  • 少しでも考え方に慣れると並行処理が楽しくなってきます
  • 実はgoroutineが分からないという人は見たことがないですが、channelは多くの人がつまずくポイントです

goroutine

Goで並行処理をしたい場合はgoroutineを使う。使い方は簡単で関数の前にgoと書くだけ。

go MyFunc()

// 無名関数で処理を書くこともよくやる
go func() {//
	// 色々処理を書く
}()

本当にこれだけで並行処理ができます。これで並行処理が書けます!!!

……これで終われば話は簡単なのですが、これで終わりません。

package main

import (
	"fmt"
	"time"
)

func main() {
	go func() {
		fmt.Println("Hello!!!")
	}()
}
実行結果 何も出力されません!goroutine内の処理が実行される前にプログラム自体が終了するからです。例えば以下のようにSleepすれば実行されます。
package main

import (
	"fmt"
	"time"
)

func main() {
	go func() {
		fmt.Println("Hello!!!")
	}()

	time.Sleep(time.Second)
}

以下のコードの実行結果は何だと思いますか?

package main

import (
	"fmt"
	"time"
)

func main() {
	tmp := 0

	for i := 0; i < 100000; i++ {
		go func() {
			tmp++
		}()
	}

	time.Sleep(time.Second)
	fmt.Println(tmp)
}
実行結果
$ ./main
91922
$ ./main
92015
$ ./main
92465
$ ./main
92374

channelについて

そもそもtime.Sleepで処理を待つことは正しくありません。いつ処理が終了するか分からないので処理の終了まで待つべきです。

処理を待つ時によく使われるのがchannelです。channelを使うことでgoroutine間で通信することができます。この辺りからつまずく人が出てきます。

package main

import "fmt"

func main() {
	c := make(chan int)
	go func() {
		// 送信
		c <- 100
	}()

	// 受信
	i := <-c
	fmt.Println(i)
}

出力結果:

100

以下のコードを実行すると

package main

import "fmt"

func main() {
	c := make(chan int)
	go func() {
		// c <- 100
	}()

	i := <-c
	fmt.Println(i)
}

デッドロックになるので異常終了します

fatal error: all goroutines are asleep - deadlock!

以下のコードを実行したらどうなるでしょうか?

for i := 0; i < 10000; i++ {
	go func(i int) {
		// 何か重い処理をする
	}(i)
}
実行結果 重い処理が同時に10000個同時に実行されます。本当に重い処理だった場合マシンが死にます。

channelを使ってみます。channelにはcapacityの概念があります。

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan struct{}, 3)

	for i := 0; i < 10000; i++ {
		c <- struct{}{}
		go func(i int) {
			defer func() {
				<-c
			}()

			time.Sleep(time.Second)
			fmt.Println(i)
		}(i)
	}
}

他にも色んなパターンがあります。最初は意味不明かもしれませんが、分かってくると楽しくなってきます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment