Kodamayuto2001 / Concurrency-in-Go

Go言語の並行処理についてアウトプットするためのレポジトリ

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Concurrency-in-Go

本レポジトリは、「Go言語による並行処理」(オライリー・ジャパン発行、ISBN978-4-87311-846-8)書籍を拝読し、アウトプットするための場所である。

テストする際は以下の通り。

  テストファイル名:example_test.go
  ベンチマークの関数:BenchmarkExample(b *testing.B) {}
  実行方法:go test -bench . -benchmem

○○という機能を実現するために○○した結果、○○というエラーが出た。

上記の場合、以下のようなアプローチで解決することが現時点では、最適解だと検証した結果考えられる。

  1. 次の通り、所望の結果を実現したいものに対して、分割を行う。(分割する粒度に関しては、所望の結果を実現するまでの時間が最も早かったものを正、遅かったものを誤として分割する粒度の閾値を最適なものに調整する)

    • 小機能A
    • 小機能B
    • 小機能C
  2. 上記の分割したものに対して下記の例の通り、それぞれが期待する結果になるかどうかを実験する。

    • 小機能A: 期待する結果
    • 小機能B: 期待しない結果
    • 小機能C: 期待する結果
  3. 1と2を繰り返し、期待しない結果がどこで起きているかどうかのスコープを小さくする。(どのくらい小さくすればよいか→1と同じで結果論となってしまうが、今までの所望の動作を実現するまでにかかった時間が最も早い実績があるアプローチを基準とする。実験と結果を繰り返すたびに基準となる閾値を大小に調整していく)

  4. 小さくした期待しない結果となる原因が存在するスコープに対して、解決するための仮説を次の例のように立てる。

    • 仮説1
    • 仮説2
    • 仮説3
  5. 仮説を検証し、期待するものとなるか否かを確認するために、仮説を具現化する。ここでいう具現化とは具体的には次の通り。

    • プログラミングの場合
      Googleの検索エンジンや書籍などから調査しながら、関数やクラスなどが正常に動くかどうかのテストのプログラムを書き、コンパイルなどして実行することが具現化にあたる。
    • 他人のフィードバックの場合
      あの人ってたぶん○○じゃないのかな?
      →その人に○○について確認するという行動が具現化にあたる。
      あなたには○○なんてできないだろう。
      →○○について実際にやってみて、自分ができるかどうかを検証するという行動が具現化にあたる。
  6. 仮説の内容、検証した結果の事実、考察や次の仮説などの実験内容に関して失念することがないように、記録する。またチームを組んでいる場合に関しては、実験内容を共有する。

  7. 結果が出るまで繰り返す。

具体的な実験内容の言語化は以下の例の通り。

//	実験11
package main

import (
	"fmt"
)

/*
	チャネルを閉じるとは
	- 「Go言語による並行処理」69ページ

	プログラムに置いて、もうこれ以上チャネルから値が送られてこないということを示せるのはとても便利です。これによって下流のプロセスが先に進んでいいのか、終了していいのか、通信を新しいチャネル、あるいは別のチャネルで再開していいのかといったタイミングを知ることができます。これはチャネルの型ごとに特別な値を用意することでも通知できますが、開発者がその都度そうした作業をおこなうのは冗長です。また、そうした機能はチャネルに本来備わっているべきもので、データ型で解決すべきものではありません。そうした意味で、チャネルを閉じるというのは、普遍的な見張りのようなもので、「おい、上流はこれ以上値を書き込まないぞ、あとは好きにしろ」と教えてくれます。チャネルを閉じるには、次のようにcloseというキーワードを使います。

	valueStream := make(chan interface{})
	close(valueStream)

	個人の意見・考え
	以下のプログラムはcloseを使ってチャネルを閉じることはできなかったが(検証不足でもしかしたらできるかもしれない)、疑似的にチャネルを閉じることを再現することができる。
	チャネルを閉じるのは、下流にもう値を書き込まないことを知らせることが目的なので、その目的を達成する他の方法でプログラミングすればよいのではないかと考えている。
*/
func main() {
	//	たぶんチャネルをクローズすることができないので、消費者のほうでクローズする。
	productClosure := func() <-chan int {
		c := make(chan int)

		//	[仮説]
		//	ここでチャネルをクローズした場合、100個の作成したゴルーチンがこの関数を抜ける前に
		//	スケジューリングされるとは限らない(ループがゴルーチンを実行するよりも早く終了するかもしれない)のでチャネルをクローズするのはできないのではないか?
		//	[結果]
		//	panic: send on closed channel
		//	やはり、チャネルを閉じるほうが早かった。
		//	defer close(c)

		for i := 0; i < 100; i++ {
			//	ゴルーチンを100個作成する。
			go func(i int) {
				c <- i
			}(i)
		}

		return c
	}

	consumerClosure := func(c <-chan int) {
		//	[仮説]
		//	この関数を抜けるときにチャネルをクローズするとうまくいくのでは?
		//	ループは必ず実行されるということが実験10でわかっている。

		//	[結果]
		//	ビルドした際に以下のエラーが出る。
		//	invalid operation: close(c) (cannot close receive-only channel)
		//	読み込み専用チャネルは閉じることができないよ
		//	defer close(c)

		for i := 0; i < 100; i++ {
			//	メインゴルーチンは読み込めるまで待機することになる。
			//	100回読み込まれることが実験10で確認できている。
			fmt.Printf("%v\n", <-c)
		}
	}

	c := productClosure()
	consumerClosure(c)
}

/*
	実行結果(実行するたびに結果が変わる)
		5
		0
		1
		2
		3
		4
		36
		6
		7
		8
		9
		10
		11
		12
		13
		14
		15
		16
		17
		18
		19
		20
		21
		22
		23
		24
		25
		26
		27
		28
		29
		30
		31
		32
		33
		34
		35
		62
		37
		38
		39
		40
		41
		42
		43
		44
		45
		46
		47
		48
		49
		50
		51
		52
		53
		54
		55
		56
		57
		58
		59
		60
		61
		89
		79
		80
		81
		82
		83
		84
		85
		86
		87
		88
		70
		63
		64
		65
		66
		67
		68
		69
		74
		71
		72
		73
		76
		75
		77
		94
		90
		91
		92
		93
		97
		95
		96
		98
		99
		78
*/

結果を出すまで繰り返す(重要)

結果を出すまで繰り返す(重要)

結果を出すまで繰り返す(重要)

About

Go言語の並行処理についてアウトプットするためのレポジトリ


Languages

Language:Go 100.0%