├── .gitignore
├── README.md
├── by-nc.eu.png
├── skeleton
├── step01
│ ├── README.md
│ ├── go.mod
│ └── main.go
├── step02
│ ├── README.md
│ ├── go.mod
│ ├── main.go
│ └── trace.png
├── step03
│ ├── README.md
│ ├── go.mod
│ ├── main.go
│ └── trace.png
├── step04
│ ├── README.md
│ ├── go.mod
│ ├── main.go
│ └── trace.png
├── step05
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── step06
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── solution
├── step01
├── README.md
├── go.mod
└── main.go
├── step02
├── README.md
├── go.mod
├── main.go
└── trace.png
├── step03
├── README.md
├── go.mod
├── main.go
└── trace.png
├── step04
├── README.md
├── go.mod
├── main.go
└── trace.png
├── step05
├── README.md
├── go.mod
├── go.sum
└── main.go
└── step06
├── README.md
├── go.mod
├── go.sum
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *.out
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 分かるゴールーチンとチャネル
2 |
3 | ## 解説資料
4 |
5 | * http://tenn.in/goroutine
6 |
7 | ## はじめに
8 |
9 | このハンズオンでは、コーヒーを淹れるという作業を例にゴールーチンを扱います。
10 | 基本的な文法などは、このハンズオンでは扱いません。
11 | そのため、ハンズオンを始める前に、[A Tour of Go](https://go-tour-jp.appspot.com)を終わらせると良いでしょう。
12 | また、文法や周辺ツール、`GOPATH`などの詳しい説明は[公式ドキュメント](https://golang.org/doc/)の「Learning Go」の項目を読んでください。
13 | 特に他のオブジェクト指向言語などを学習されている方は、「FAQ」に目を通すとよいでしょう。
14 |
15 | 英語が辛い方は、有志によって[翻訳されたドキュメント](http://golang-jp.org/doc/)の「Goを学ぶ」を読んで下さい。
16 | すべてが翻訳されているわけではありませんが、役に立つでしょう。
17 | また、[はじめてのGo](http://gihyo.jp/dev/feature/01/go_4beginners)も日本語で書かれていて分かりやすいのでぜひ読んで下さい。
18 |
19 | 標準パッケージについては、[パッケージドキュメント](https://golang.org/pkg/)を見ると、使い方が説明されています。
20 |
21 | ## 学べることと学べないこと
22 |
23 | このハンズオンを行うと以下のことが学べます。
24 |
25 | * ゴールーチンとチャネルの基本
26 | * トレースの方法
27 | * `sync`パッケージの使い方
28 | * ゴールーチンとエラー処理
29 | * コンテキストとキャンセル処理
30 |
31 | 一方、学べないことは以下のとおりです。
32 |
33 | * Goの開発環境のインストール方法
34 | * IDEやエディタの設定
35 | * 基本的な文法
36 | * コマンドラインツールの作り方
37 | * net/httpパッケージ
38 |
39 | ## ハンズオンのやりかた
40 |
41 | `skeleton`ディレクトリ以下に問題があり、6つのステップに分けられています。
42 | STEP 1からSTEP 6までステップごとに進めていくことで、並行処理に関する知識が学べます。
43 |
44 | 各ステップに、READMEが用意されていますので、まずは`README`を読みます。
45 | `README`には、そのステップを理解するための解説が書かれています。
46 |
47 | `README`を読んだら、ソースコードを開き`TODO`コメントが書かれている箇所をコメントに従って修正して行きます。
48 | `TODO`コメントをすべて修正し終わったら、`README`に書かれた実行例に従ってプログラムをコンパイルして実行します。
49 |
50 | 途中でわからなくなった場合は、`solution`ディレクトリ以下に解答例を用意していますので、そちらをご覧ください。
51 |
52 | `Mac`と`Windows`で動作確認をしていますが、解説は`Mac`の動作結果をもとに解説しています。
53 | `Windows`の方は、パスの区切り文字等を適宜読み替えてください。
54 |
55 | ## 目次
56 |
57 | * STEP 1: [ゴールーチンを使わずに処理する](./skeleton/step01)([解答例](./solution/step01))
58 | * STEP 2: [ボトルネックを探す](./skeleton/step02)([解答例](./solution/step02))
59 | * STEP 3: [ゴールーチンとチャネル](./skeleton/step03)([解答例](./solution/step03))
60 | * STEP 4: [syncパッケージを使う](./skeleton/step04)([解答例](./solution/step04))
61 | * STEP 5: [ゴールーチンとエラー処理](./skeleton/step05)([解答例](./solution/step05))
62 | * STEP 6: [コンテキストとキャンセル処理](./skeleton/step06)([解答例](./solution/step06))
63 |
64 | ## ハンズオンの開催や資料の扱いについて
65 | この資料を元にハンズオンを開催するために@tenntennの許可などはいりません。
66 | 好きに開催してください。
67 | @tenntennの行ける範囲であれば、開催するから解説して欲しいという依頼もウェルカムです。
68 |
69 | なお、forkして変更してもらっても構いませんが、できればPRをいただけると嬉しいです。
70 | 資料に間違えを発見した方もissueやPRを頂ければ対応します。
71 |
72 | ## ライセンス
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/by-nc.eu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/by-nc.eu.png
--------------------------------------------------------------------------------
/skeleton/step01/README.md:
--------------------------------------------------------------------------------
1 | # STEP 1: ゴールーチンを使わずに処理する
2 |
3 | ## コーヒーを淹れるプログラムを作ろう
4 |
5 | このハンズオンでは、題材としてコーヒーを淹れるプログラムを作ります。
6 | 実際にはコーヒーを淹れるわけではありませんが、一連の手順をいかに並行処理で効率化していくかということを学ぶことができます。
7 |
8 | コーヒーを淹れるには次の手順が必要でしょう。
9 |
10 | * お湯を沸かす
11 | * 豆を挽く
12 | * コーヒーを淹れる
13 |
14 | ## データ型を定義しよう
15 |
16 | コーヒーを淹れるには、お湯と挽かれたコーヒー豆の粉が必要になります。
17 | お湯を沸かすには水が必要になり、コーヒー豆の粉を手に入れるには、コーヒー豆が必要です。
18 | つまり、各手順で次のデータの変換が行われます。
19 |
20 | * お湯を沸かす: 水 -> お湯
21 | * 豆を挽く: コーヒー豆 -> 挽かれたコーヒー豆の粉
22 | * コーヒーを淹れる: お湯, 挽かれたコーヒー豆の粉 -> コーヒー
23 |
24 | このプログラムには、水、お湯、豆、挽かれた豆、コーヒーの5つの種類のデータが存在することがわかります。
25 | これらのデータを表すために、データ型を作成しましょう。
26 | データ型は`type`を用いて定義することができます。
27 |
28 | ```go
29 | type (
30 | Bean int // 豆
31 | GroundBean int // 挽かれた豆
32 | Water int // 水
33 | HotWater int // お湯
34 | Coffee int // コーヒー
35 | )
36 | ```
37 |
38 | また、利便性のために次のようにいくつか定数も用意しておきます。
39 | こうすることで、`10 * GramBeans`のように記述することができます。
40 |
41 | ```go
42 | const (
43 | GramBeans Bean = 1
44 | GramGroundBeans GroundBean = 1
45 | MilliLiterWater Water = 1
46 | MilliLiterHotWater HotWater = 1
47 | CupsCoffee Coffee = 1
48 | )
49 | ```
50 |
51 | つぎに、N杯のコーヒーを淹れるために必要な材料の分量を返すメソッドを用意します。
52 | メソッドは、`Coffee`型のメソッドとして設けます。
53 | こうすることで、2杯のコーヒーに必要な水の分量を`(2 * Cupscoffee).Water()`のように取得することができます。
54 |
55 | ```go
56 | // 1カップのコーヒーを淹れるのに必要な水の量
57 | func (cups Coffee) Water() Water {
58 | return Water(180*cups) / MilliLiterWater
59 | }
60 |
61 | // 1カップのコーヒーを淹れるのに必要なお湯の量
62 | func (cups Coffee) HotWater() HotWater {
63 | return HotWater(180*cups) / MilliLiterHotWater
64 | }
65 |
66 | // 1カップのコーヒーを淹れるのに必要な豆の量
67 | func (cups Coffee) Beans() Bean {
68 | return Bean(20*cups) / GramBeans
69 | }
70 |
71 | // 1カップのコーヒーを淹れるのに必要な粉の量
72 | func (cups Coffee) GroundBeans() GroundBean {
73 | return GroundBean(20*cups) / GramGroundBeans
74 | }
75 | ```
76 |
77 | ## お湯を沸かす
78 |
79 | お湯を沸かす関数`boil`を作成します。
80 | `boil`は一定時間立つと、引数で与えた分量と同じ量のお湯を返します。
81 |
82 | ```go
83 | // お湯を沸かす
84 | func boil(water Water) HotWater {
85 | time.Sleep(400 * time.Millisecond)
86 | return HotWater(water)
87 | }
88 | ```
89 |
90 | ## コーヒー豆を挽く
91 |
92 | コーヒー豆を挽く関数`grind`を作ります。
93 | `grind`は引数にコーヒー豆を受け取り、一定時間後に挽いた豆を返します。
94 |
95 | ```go
96 | // コーヒー豆を挽く
97 | func grind(beans Bean) GroundBean {
98 | time.Sleep(200 * time.Millisecond)
99 | return GroundBean(beans)
100 | }
101 | ```
102 |
103 | ## コーヒーを淹れる
104 |
105 | コーヒーを淹れる関数`brew`を作ります。
106 | `brew`は引数にお湯と挽いた豆を受け取り、一定時間後にコーヒーを返します。
107 |
108 | ```go
109 | // コーヒーを淹れる
110 | func brew(hotWater HotWater, groundBeans GroundBean) Coffee {
111 | time.Sleep(1 * time.Second)
112 | // 少ない方を優先する
113 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
114 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
115 | if cups1 < cups2 {
116 | return cups1
117 | }
118 | return cups2
119 | }
120 | ```
121 |
122 | ## 処理をまとめる
123 |
124 | `main`関数から、`boil`関数、`grind`関数、`brew`関数を順に呼び、コーヒーを淹れます。
125 | 材料は次のように20杯分のコーヒーとしてます。
126 |
127 | ```go
128 | // 作るコーヒーの数
129 | const amountCoffee = 20 * CupsCoffee
130 |
131 | // 材料
132 | water := amountCoffee.Water()
133 | beans := amountCoffee.Beans()
134 | ```
135 |
136 | 一度に沸かせるお湯の量や挽ける豆の量、淹れれるコーヒーの量は次のように決まっているものとします。
137 |
138 | * 一度に沸かせるお湯の量: 600[ml]
139 | * 一度に挽ける豆の量: 20[g]
140 | * 一度に淹れれるコーヒー: 4杯
141 |
142 | `boil`関数、`grind`関数、`brew`関数を複数回呼び出すことで、20杯のコーヒーを淹れることができます。
143 |
144 | ## 実行
145 |
146 | `TODO`を埋めると次のコマンドで実行することができます。
147 |
148 | ```
149 | $ go run main.go
150 | ```
151 |
152 | 次のように表示されれば成功です。
153 |
154 | ```
155 | 3600[ml] water
156 | 400[g] beans
157 | 3600[ml] hot water
158 | 400[g] ground beans
159 | 20 cup(s) coffee
160 | ```
161 |
--------------------------------------------------------------------------------
/skeleton/step01/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step01
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/skeleton/step01/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | type (
9 | Bean int
10 | GroundBean int
11 | Water int
12 | HotWater int
13 | Coffee int
14 | )
15 |
16 | const (
17 | GramBeans Bean = 1
18 | GramGroundBeans GroundBean = 1
19 | MilliLiterWater Water = 1
20 | MilliLiterHotWater HotWater = 1
21 | CupsCoffee Coffee = 1
22 | )
23 |
24 | func (w Water) String() string {
25 | return fmt.Sprintf("%d[ml] water", int(w))
26 | }
27 |
28 | func (hw HotWater) String() string {
29 | return fmt.Sprintf("%d[ml] hot water", int(hw))
30 | }
31 |
32 | func (b Bean) String() string {
33 | return fmt.Sprintf("%d[g] beans", int(b))
34 | }
35 |
36 | func (gb GroundBean) String() string {
37 | return fmt.Sprintf("%d[g] ground beans", int(gb))
38 | }
39 |
40 | func (cups Coffee) String() string {
41 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
42 | }
43 |
44 | // 1カップのコーヒーを淹れるのに必要な水の量
45 | func (cups Coffee) Water() Water {
46 | return Water(180*cups) / MilliLiterWater
47 | }
48 |
49 | // 1カップのコーヒーを淹れるのに必要なお湯の量
50 | func (cups Coffee) HotWater() HotWater {
51 | return HotWater(180*cups) / MilliLiterHotWater
52 | }
53 |
54 | // 1カップのコーヒーを淹れるのに必要な豆の量
55 | func (cups Coffee) Beans() Bean {
56 | return Bean(20*cups) / GramBeans
57 | }
58 |
59 | // 1カップのコーヒーを淹れるのに必要な粉の量
60 | func (cups Coffee) GroundBeans() GroundBean {
61 | return GroundBean(20*cups) / GramGroundBeans
62 | }
63 |
64 | // お湯を沸かす
65 | func boil(water Water) HotWater {
66 | time.Sleep(400 * time.Millisecond)
67 | return HotWater(water)
68 | }
69 |
70 | // コーヒー豆を挽く
71 | func grind(beans Bean) GroundBean {
72 | time.Sleep(200 * time.Millisecond)
73 | return GroundBean(beans)
74 | }
75 |
76 | // コーヒーを淹れる
77 | func brew(hotWater HotWater, groundBeans GroundBean) Coffee {
78 | time.Sleep(1 * time.Second)
79 | // 少ない方を優先する
80 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
81 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
82 | if cups1 < cups2 {
83 | return cups1
84 | }
85 | return cups2
86 | }
87 |
88 | func main() {
89 | // 作るコーヒーの数
90 | const amountCoffee = 20 * CupsCoffee
91 |
92 | // 材料
93 | water := amountCoffee.Water()
94 | beans := amountCoffee.Beans()
95 |
96 | fmt.Println(water)
97 | fmt.Println(beans)
98 |
99 | // お湯を沸かす
100 | var hotWater HotWater
101 | for water > 0 {
102 | // TODO: 関数水を600[ml]減らす
103 | // TODO: お湯をboil関数で600[ml]沸かして増やす
104 | }
105 | fmt.Println(hotWater)
106 |
107 | // 豆を挽く
108 | var groundBeans GroundBean
109 | for beans > 0 {
110 | // TODO: 豆を20[g]減らす
111 | // TODO: 挽いた豆をgrind関数で20[g]挽いて増やす
112 | }
113 | fmt.Println(groundBeans)
114 |
115 | // コーヒーを淹れる
116 | var coffee Coffee
117 | cups := 4 * CupsCoffee
118 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
119 | // TODO: お湯を4杯に必要な分量だけ減らす
120 | // TODO: 挽いた豆を4杯に必要な分量だけ減らす
121 | // TODO: 4杯分の材料でbrew関数でコーヒーを淹れて増やす
122 | }
123 |
124 | fmt.Println(coffee)
125 | }
126 |
--------------------------------------------------------------------------------
/skeleton/step02/README.md:
--------------------------------------------------------------------------------
1 | # STEP 2: ボトルネックを探す
2 |
3 | ## トレースを行う
4 |
5 | STEP 1で作成したプログラムはなんとなく遅い気がします。
6 | STEP 2では、`runtime/trace`パッケージを用いてトレースし、ボトルネックとなる部分を探します。
7 |
8 | まずは、`main`関数をトレースするために、`main`関数の中身を`_main`関数に移動させます。
9 |
10 | そして、トレース対象の処理を開始する前に、`trace.Start`関数を呼び出し、終了後に`trace.Stop`を呼び出します。
11 | なお、`defer`を用いることで、関数の終了時に呼び出すことができます。
12 |
13 | ```go
14 | func main() {
15 | f, err := os.Create("trace.out")
16 | if err != nil {
17 | log.Fatalln("Error:", err)
18 | }
19 | defer func() {
20 | if err := f.Close(); err != nil {
21 | log.Fatalln("Error:", err)
22 | }
23 | }()
24 |
25 | if err := trace.Start(f); err != nil {
26 | log.Fatalln("Error:", err)
27 | }
28 | defer trace.Stop()
29 |
30 | _main()
31 | }
32 | ```
33 |
34 | ## ユーザアノテーション
35 |
36 | Go1.11からユーザアノテーションという機能が入りました。
37 | ユーザが任意のタイミングでトレース情報を出力することができる機能です。
38 |
39 | ユーザアノテーションは、以下の単位で行うことができます。
40 |
41 | * Task
42 | * Region
43 | * Log
44 |
45 | 1つのTaskに複数のRegionがあり、Regionの中でLogを出すというイメージです。
46 |
47 | ここでは、Taskは「コーヒーを淹れること」、Regionは「お湯を沸かす」、「豆を挽く」、「コーヒー淹れる」の3つになります。
48 |
49 | `Task`は次のように作成することができます。
50 | 作成した`Task`は、`End`メソッドを呼び出すまでの処理が対象となります。
51 | 通常は`defer`で呼び出すことで関数の終了時に呼び出します。
52 |
53 | ```go
54 | ctx, task := trace.NewTask(context.Background(), "make coffee")
55 | defer task.End()
56 | ```
57 |
58 | `trace.NewTask`関数の第1引数で返される`context.Context`型の値には、Taskの情報が保持されています。
59 |
60 | Regionは次のように、`trace.StartRegion`を呼び出すことで作ることができます。
61 | `trace.StartRegion`の第1引数に`trace.NewTask`関数で返ってきた`context.Context`を渡すことで、TaskとRegionを紐付けることができます。
62 |
63 | 作成したRegionは、`End`メソッドを呼び出すまでの処理が対象となります。
64 |
65 | ```go
66 | region := trace.StartRegion(ctx, "region_name")
67 | defer region.End()
68 | ```
69 |
70 | なお、次のように1行で書くこともできます。
71 |
72 | ```go
73 | defer trace.StartRegion(ctx, "region_name").End()
74 | ```
75 |
76 | ## 実行とトレースデータの表示
77 |
78 | `TODO`を埋めると次のコマンドで実行することができます。
79 |
80 | ```
81 | $ go run main.go
82 | ```
83 |
84 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
85 |
86 | ```
87 | $ go tool trace trace.out
88 | ```
89 |
90 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
91 |
92 |
93 |
94 | 図を見ると、`boil`、`grind`、`brew`が直列に処理されていることがわかります。
95 | `boil`と`grind`は同時に行っても問題ないので、ここを改善すれば早くなりそうです。
96 |
--------------------------------------------------------------------------------
/skeleton/step02/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step02
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/skeleton/step02/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "time"
10 | )
11 |
12 | type (
13 | Bean int
14 | GroundBean int
15 | Water int
16 | HotWater int
17 | Coffee int
18 | )
19 |
20 | const (
21 | GramBeans Bean = 1
22 | GramGroundBeans GroundBean = 1
23 | MilliLiterWater Water = 1
24 | MilliLiterHotWater HotWater = 1
25 | CupsCoffee Coffee = 1
26 | )
27 |
28 | func (w Water) String() string {
29 | return fmt.Sprintf("%d[ml] water", int(w))
30 | }
31 |
32 | func (hw HotWater) String() string {
33 | return fmt.Sprintf("%d[ml] hot water", int(hw))
34 | }
35 |
36 | func (b Bean) String() string {
37 | return fmt.Sprintf("%d[g] beans", int(b))
38 | }
39 |
40 | func (gb GroundBean) String() string {
41 | return fmt.Sprintf("%d[g] ground beans", int(gb))
42 | }
43 |
44 | func (cups Coffee) String() string {
45 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
46 | }
47 |
48 | // 1カップのコーヒーを淹れるのに必要な水の量
49 | func (cups Coffee) Water() Water {
50 | return Water(180*cups) / MilliLiterWater
51 | }
52 |
53 | // 1カップのコーヒーを淹れるのに必要なお湯の量
54 | func (cups Coffee) HotWater() HotWater {
55 | return HotWater(180*cups) / MilliLiterHotWater
56 | }
57 |
58 | // 1カップのコーヒーを淹れるのに必要な豆の量
59 | func (cups Coffee) Beans() Bean {
60 | return Bean(20*cups) / GramBeans
61 | }
62 |
63 | // 1カップのコーヒーを淹れるのに必要な粉の量
64 | func (cups Coffee) GroundBeans() GroundBean {
65 | return GroundBean(20*cups) / GramGroundBeans
66 | }
67 |
68 | func main() {
69 | f, err := os.Create("trace.out")
70 | if err != nil {
71 | log.Fatalln("Error:", err)
72 | }
73 | defer func() {
74 | if err := f.Close(); err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | }()
78 |
79 | if err := trace.Start(f); err != nil {
80 | log.Fatalln("Error:", err)
81 | }
82 | defer trace.Stop()
83 |
84 | _main()
85 | }
86 |
87 | func _main() {
88 | // 作るコーヒーの数
89 | const amountCoffee = 20 * CupsCoffee
90 |
91 | ctx, task := trace.NewTask(context.Background(), "make coffee")
92 | defer task.End()
93 |
94 | // 材料
95 | water := amountCoffee.Water()
96 | beans := amountCoffee.Beans()
97 |
98 | fmt.Println(water)
99 | fmt.Println(beans)
100 |
101 | // お湯を沸かす
102 | var hotWater HotWater
103 | for water > 0 {
104 | water -= 600 * MilliLiterWater
105 | hotWater += boil(ctx, 600*MilliLiterWater)
106 | }
107 | fmt.Println(hotWater)
108 |
109 | // 豆を挽く
110 | var groundBeans GroundBean
111 | for beans > 0 {
112 | beans -= 20 * GramBeans
113 | groundBeans += grind(ctx, 20*GramBeans)
114 | }
115 | fmt.Println(groundBeans)
116 |
117 | // コーヒーを淹れる
118 | var coffee Coffee
119 | cups := 4 * CupsCoffee
120 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
121 | hotWater -= cups.HotWater()
122 | groundBeans -= cups.GroundBeans()
123 | coffee += brew(ctx, cups.HotWater(), cups.GroundBeans())
124 | }
125 |
126 | fmt.Println(coffee)
127 | }
128 |
129 | // お湯を沸かす
130 | func boil(ctx context.Context, water Water) HotWater {
131 | // TODO: "boil"という名前のRegionを作成
132 | time.Sleep(400 * time.Millisecond)
133 | return HotWater(water)
134 | }
135 |
136 | // コーヒー豆を挽く
137 | func grind(ctx context.Context, beans Bean) GroundBean {
138 | // TODO: "grind"という名前のRegionを作成
139 | time.Sleep(200 * time.Millisecond)
140 | return GroundBean(beans)
141 | }
142 |
143 | // コーヒーを淹れる
144 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) Coffee {
145 | // TODO: "brew"という名前のRegionを作成
146 | time.Sleep(1 * time.Second)
147 | // 少ない方を優先する
148 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
149 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
150 | if cups1 < cups2 {
151 | return cups1
152 | }
153 | return cups2
154 | }
155 |
--------------------------------------------------------------------------------
/skeleton/step02/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/skeleton/step02/trace.png
--------------------------------------------------------------------------------
/skeleton/step03/README.md:
--------------------------------------------------------------------------------
1 | # STEP 3: ゴールーチンとチャネル
2 |
3 | ## ゴールーチン
4 |
5 | Goには、ゴールーチンという並行処理を行うための機能があります。
6 | 次のように、`go`というキーワードを用いて関数呼び出しを行うと新しいゴールーチンで関数を呼び出すことができます。
7 |
8 | ```go
9 | go f()
10 | ```
11 |
12 | ゴールーチンは、非常に軽量でOSのスレッドの上に複数動きます。
13 | デフォルトでは、論理CPU数と同じ数だけ並列に動くことが可能です。
14 | 複数のゴールーチンの処理をどのように切り替えるかについては、Goのランタイムがスケジューリングを行うため、ユーザは気にする必要がありません。
15 |
16 | ## チャネル
17 |
18 | ゴールーチンを用いると関数を独立して動作させることができます。
19 | ゴールーチン間のデータのやりとりはどのように行えばよいのでしょうか?
20 |
21 | 次のように、共有の変数を用いる方法はどうでしょうか。
22 |
23 | ```go
24 | func main() {
25 | var done bool
26 | go func() {
27 | // 重たい処理
28 | time.Sleep(2 * time.Second)
29 | done := true
30 | }()
31 |
32 | // 処理が終わるまで待つ
33 | for done {
34 | time.Sleep(10 * time.Millisecond)
35 | }
36 | }
37 | ```
38 |
39 | この例では、1つのゴールーチンでしか変数`done`に書き込んでいないので大きな問題は起きませんが、共有の変数に同時に読み書きが発生すると競合が起きてしまいます。
40 |
41 | そこでGoでは、ゴールーチン間のデータのやりとりにチャネルというデータ構造を用います。
42 | チャネルはゴールーチン間の通信を行うデータ型で次のように利用することができます。
43 |
44 | ```go
45 | func main() {
46 | done := make(chan bool)
47 | go func() {
48 | // 重たい処理
49 | time.Sleep(2 * time.Second)
50 | done <- true
51 | }()
52 |
53 | // 処理が終わるまで待つ
54 | <-done
55 | }
56 | ```
57 |
58 | チャネルの初期化には、組込みの`make`関数を用います。
59 | `make`関数には、`chan bool`のようなチャネルを表す型を指定します。
60 | `chan bool`は`bool`型のデータをやり取りするためのチャネルを表します。
61 |
62 | `done <- true`のように書くことでチャネルに値を送ることができます。
63 | また、`<-done`のように書くと、チャネルから値を受信することができます。
64 |
65 | チャネルの送受信ではデータが受信・送信されるまで処理がブロックされます。
66 | なお、ここでは扱いませんがチャネルに空きバッファがある場合はブロックされずに処理が進みます。
67 |
68 | チャネルは、引数や戻り値に渡すことができます。
69 | その際、`chan bool`のように型を書くと送受信可能なチャネルになってしまうため、
70 | 引数の場合には`chan<- bool`、戻り値の場合には`<-chan bool`と書くことで、受信または送信限定のチャネルとすることができます。
71 |
72 | ## 実行とトレースデータの表示
73 |
74 | `TODO`を埋めると次のコマンドで実行することができます。
75 |
76 | ```
77 | $ go run main.go
78 | ```
79 |
80 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
81 |
82 | ```
83 | $ go tool trace trace.out
84 | ```
85 |
86 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
87 |
88 |
89 |
90 | 実行時間が直列に行うより大幅に改善されていることがわかります。
91 |
92 | `boil`と`grind`が並列に処理され、その後に`brew`が処理されています。
93 | `boil`と`grind`のバーの長さが同じな理由は、`grind`の処理が終わっても`boil`の結果がすべて受け取るまでは、`grind`の結果を受け取ってもらえないためです。
94 |
--------------------------------------------------------------------------------
/skeleton/step03/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step03
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/skeleton/step03/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "time"
10 | )
11 |
12 | type (
13 | Bean int
14 | GroundBean int
15 | Water int
16 | HotWater int
17 | Coffee int
18 | )
19 |
20 | const (
21 | GramBeans Bean = 1
22 | GramGroundBeans GroundBean = 1
23 | MilliLiterWater Water = 1
24 | MilliLiterHotWater HotWater = 1
25 | CupsCoffee Coffee = 1
26 | )
27 |
28 | func (w Water) String() string {
29 | return fmt.Sprintf("%d[ml] water", int(w))
30 | }
31 |
32 | func (hw HotWater) String() string {
33 | return fmt.Sprintf("%d[ml] hot water", int(hw))
34 | }
35 |
36 | func (b Bean) String() string {
37 | return fmt.Sprintf("%d[g] beans", int(b))
38 | }
39 |
40 | func (gb GroundBean) String() string {
41 | return fmt.Sprintf("%d[g] ground beans", int(gb))
42 | }
43 |
44 | func (cups Coffee) String() string {
45 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
46 | }
47 |
48 | // 1カップのコーヒーを淹れるのに必要な水の量
49 | func (cups Coffee) Water() Water {
50 | return Water(180*cups) / MilliLiterWater
51 | }
52 |
53 | // 1カップのコーヒーを淹れるのに必要なお湯の量
54 | func (cups Coffee) HotWater() HotWater {
55 | return HotWater(180*cups) / MilliLiterHotWater
56 | }
57 |
58 | // 1カップのコーヒーを淹れるのに必要な豆の量
59 | func (cups Coffee) Beans() Bean {
60 | return Bean(20*cups) / GramBeans
61 | }
62 |
63 | // 1カップのコーヒーを淹れるのに必要な粉の量
64 | func (cups Coffee) GroundBeans() GroundBean {
65 | return GroundBean(20*cups) / GramGroundBeans
66 | }
67 |
68 | func main() {
69 | f, err := os.Create("trace.out")
70 | if err != nil {
71 | log.Fatalln("Error:", err)
72 | }
73 | defer func() {
74 | if err := f.Close(); err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | }()
78 |
79 | if err := trace.Start(f); err != nil {
80 | log.Fatalln("Error:", err)
81 | }
82 | defer trace.Stop()
83 |
84 | _main()
85 | }
86 |
87 | func _main() {
88 | // 作るコーヒーの数
89 | const amountCoffee = 20 * CupsCoffee
90 |
91 | ctx, task := trace.NewTask(context.Background(), "make coffe")
92 | defer task.End()
93 |
94 | // 材料
95 | water := amountCoffee.Water()
96 | beans := amountCoffee.Beans()
97 |
98 | fmt.Println(water)
99 | fmt.Println(beans)
100 |
101 | hwch := make(chan HotWater)
102 | gbch := make(chan GroundBean)
103 | cfch := make(chan Coffee)
104 |
105 | // お湯を沸かす
106 | var hwCount int
107 | for water > 0 {
108 | hwCount++
109 | water -= 600 * MilliLiterWater
110 | // TODO: ゴールーチンでboil関数を呼び出す
111 | }
112 |
113 | // 豆を挽く
114 | var gbCount int
115 | for beans > 0 {
116 | beans -= 20 * GramBeans
117 | gbCount++
118 | go grind(ctx, gbch, 20*GramBeans)
119 | }
120 |
121 | var hotWater HotWater
122 | for i := 0; i < hwCount; i++ {
123 | hotWater += <-hwch
124 | }
125 | fmt.Println(hotWater)
126 |
127 | var groundBeans GroundBean
128 | for i := 0; i < gbCount; i++ {
129 | groundBeans += <-gbch
130 | }
131 | fmt.Println(groundBeans)
132 |
133 | // コーヒーを淹れる
134 | var cfCount int
135 | cups := 4 * CupsCoffee
136 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
137 | hotWater -= cups.HotWater()
138 | groundBeans -= cups.GroundBeans()
139 | cfCount++
140 | go brew(ctx, cfch, cups.HotWater(), cups.GroundBeans())
141 | }
142 |
143 | var coffee Coffee
144 | for i := 0; i < cfCount; i++ {
145 | // TODO: チャネルから送られてくるコーヒーをcoffeeに足していく
146 | }
147 | fmt.Println(coffee)
148 | }
149 |
150 | // お湯を沸かす
151 | func boil(ctx context.Context, ch chan<- HotWater, water Water) {
152 | defer trace.StartRegion(ctx, "boil").End()
153 | time.Sleep(400 * time.Millisecond)
154 | ch <- HotWater(water)
155 | }
156 |
157 | // コーヒー豆を挽く
158 | func grind(ctx context.Context, ch chan<- GroundBean, beans Bean) {
159 | defer trace.StartRegion(ctx, "grind").End()
160 | time.Sleep(200 * time.Millisecond)
161 | // TODO: チャネルに挽いた豆を渡す
162 | }
163 |
164 | // コーヒーを淹れる
165 | func brew(ctx context.Context, ch chan<- Coffee, hotWater HotWater, groundBeans GroundBean) {
166 | defer trace.StartRegion(ctx, "brew").End()
167 | time.Sleep(1 * time.Second)
168 | // 少ない方を優先する
169 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
170 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
171 | if cups1 < cups2 {
172 | ch <- cups1
173 | } else {
174 | ch <- cups2
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/skeleton/step03/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/skeleton/step03/trace.png
--------------------------------------------------------------------------------
/skeleton/step04/README.md:
--------------------------------------------------------------------------------
1 | # STEP 4: syncパッケージを使う
2 |
3 | ## チャネルを使わないゴールーチン間のやり取り
4 |
5 | STEP 3では、ゴールーチン間でデータのやりとりを行う場合は、
6 | データの競合を避けるためにチャネルを利用するという説明を行いました。
7 |
8 | しかし、データの競合を避けるためには、チャネルを使わずロックをとって排他制御を行う方法もあります。
9 | `sync`パッケージはゴールーチンを跨いだロックなどの便利な機能を提供するパッケージです。
10 |
11 | 例えば、次のように`sync.Mutex`を用いることでロックを取ることができます。
12 |
13 | ```go
14 | var (
15 | count int
16 | mu sync.Mutex
17 | )
18 |
19 | done := make(chan bool)
20 | go func() {
21 | for i := 0; i < 10; i++ {
22 | mu.Lock()
23 | count++
24 | mu.Unlock()
25 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
26 | }
27 | done <- true
28 | }()
29 |
30 | go func() {
31 | for i := 0; i < 10; i++ {
32 | mu.Lock()
33 | count++
34 | mu.Unlock()
35 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
36 | }
37 | done <- true
38 | }()
39 |
40 | <-done
41 | <-done
42 | fmt.Println(count)
43 | ```
44 |
45 | `sync.Mutex`は`Lock`メソッドでロックを取り、`Unlock`メソッドでロックを解除します。
46 | すでにロックが掛かっているMutexに対して`Lock`メソッドを呼び出そうとすると、`Unlock`メソッドが呼び出されるまで処理がブロックされます。
47 |
48 | `Unlock`メソッドは`defer`で呼び出すこともありますが、`for`や再帰呼び出しなどでデッドロックを起こす可能性があるため注意が必要です。
49 |
50 | ## ゴールーチンの待ち合わせ
51 |
52 | 複数のゴールーチンの処理を待って次の処理に移りたい場合があります。
53 | 例えば、お湯を沸かすことと豆を挽くことは並列で行っても問題ありませんが、
54 | コーヒーを淹れるためには、お湯と挽いた豆が揃っている必要があります。
55 |
56 | `sync.WaitGroup`はゴールーチンの待ち合わせを行う機能を提供しています。
57 | 使い方はとてもシンプルです。
58 | `Wait`メソッドで複数のゴールーチンの処理を待ち合わせることができ、
59 | `Add`メソッドで追加した数だけ`Done`メソッドが呼ばれるまで処理がブロックされます。
60 |
61 | 例えば、次の例では`wg.Add(1)`が2回実行されているため、`wg.Done()`が2回実行されるまで
62 | `wg.Wait()`で処理をブロックします。
63 |
64 | ```go
65 | var (
66 | count int
67 | mu sync.Mutex
68 | )
69 |
70 | var wg sync.WaitGroup
71 | wg.Add(1)
72 | go func() {
73 | defer wg.Done()
74 | for i := 0; i < 10; i++ {
75 | mu.Lock()
76 | count++
77 | mu.Unlock()
78 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
79 | }
80 | }()
81 |
82 | wg.Add(1)
83 | go func() {
84 | defer wg.Done()
85 | for i := 0; i < 10; i++ {
86 | mu.Lock()
87 | count++
88 | mu.Unlock()
89 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
90 | }
91 | }()
92 |
93 | // 2つのゴールーチンの処理が終わるまで待つ
94 | wg.Wait()
95 | fmt.Println(count)
96 | ```
97 |
98 | ## プログラムの改造
99 |
100 | チャネルを使わないようにコーヒーを淹れるプログラムを改造してみましょう。
101 |
102 | `boil`関数、`grind`関数、`brew`関数の処理結果はチャネル経由ではなく戻り値で受け取ります。
103 | 受け取った戻り値を変数に足す必要がありますが、そのまま足すと競合が起きるためロックを取って足す必要があります。
104 |
105 | `boil`関数と`grind`関数の処理は並列で実行しても問題ないため、それぞれゴールーチンで実行し、1つの`sync.WaitGroup`で待ち合わせすることにします。
106 |
107 | `brew`関数の処理はゴールーチンで呼ばれますが、別の`sync.WaitGroup`でコーヒーが全て淹れ終わるまで待つことにしましょう。
108 |
109 | ## 実行とトレースデータの表示
110 |
111 | `TODO`を埋めると次のコマンドで実行することができます。
112 |
113 | ```
114 | $ go run main.go
115 | ```
116 |
117 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
118 |
119 | ```
120 | $ go tool trace trace.out
121 | ```
122 |
123 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
124 |
125 |
126 |
127 | 実行時間はSTEP 3と大きく変わりません。
128 | 同じように`boil`と`grind`が並列に処理され、その後に`grind`が処理されています。
129 | しかし、`boil`と`grind`のバーの長さが同じではなくなっています。
130 |
131 | STEP 3では`boil`からのデータの送信をすべて待った後に、`grind`からのデータを受け取っていましたが、
132 | 今回は`sync.WaitGroup`で`boil`も`grind`も関係なく待っていたので、`grind`の方が`boil`の長さに引っ張られずに終了したためです。
133 |
--------------------------------------------------------------------------------
/skeleton/step04/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step04
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/skeleton/step04/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "sync"
10 | "time"
11 | )
12 |
13 | type (
14 | Bean int
15 | GroundBean int
16 | Water int
17 | HotWater int
18 | Coffee int
19 | )
20 |
21 | const (
22 | GramBeans Bean = 1
23 | GramGroundBeans GroundBean = 1
24 | MilliLiterWater Water = 1
25 | MilliLiterHotWater HotWater = 1
26 | CupsCoffee Coffee = 1
27 | )
28 |
29 | func (w Water) String() string {
30 | return fmt.Sprintf("%d[ml] water", int(w))
31 | }
32 |
33 | func (hw HotWater) String() string {
34 | return fmt.Sprintf("%d[ml] hot water", int(hw))
35 | }
36 |
37 | func (b Bean) String() string {
38 | return fmt.Sprintf("%d[g] beans", int(b))
39 | }
40 |
41 | func (gb GroundBean) String() string {
42 | return fmt.Sprintf("%d[g] ground beans", int(gb))
43 | }
44 |
45 | func (cups Coffee) String() string {
46 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
47 | }
48 |
49 | // 1カップのコーヒーを淹れるのに必要な水の量
50 | func (cups Coffee) Water() Water {
51 | return Water(180*cups) / MilliLiterWater
52 | }
53 |
54 | // 1カップのコーヒーを淹れるのに必要なお湯の量
55 | func (cups Coffee) HotWater() HotWater {
56 | return HotWater(180*cups) / MilliLiterHotWater
57 | }
58 |
59 | // 1カップのコーヒーを淹れるのに必要な豆の量
60 | func (cups Coffee) Beans() Bean {
61 | return Bean(20*cups) / GramBeans
62 | }
63 |
64 | // 1カップのコーヒーを淹れるのに必要な粉の量
65 | func (cups Coffee) GroundBeans() GroundBean {
66 | return GroundBean(20*cups) / GramGroundBeans
67 | }
68 |
69 | func main() {
70 | f, err := os.Create("trace.out")
71 | if err != nil {
72 | log.Fatalln("Error:", err)
73 | }
74 | defer func() {
75 | if err := f.Close(); err != nil {
76 | log.Fatalln("Error:", err)
77 | }
78 | }()
79 |
80 | if err := trace.Start(f); err != nil {
81 | log.Fatalln("Error:", err)
82 | }
83 | defer trace.Stop()
84 |
85 | _main()
86 | }
87 |
88 | func _main() {
89 | // 作るコーヒーの数
90 | const amountCoffee = 20 * CupsCoffee
91 |
92 | ctx, task := trace.NewTask(context.Background(), "make coffe")
93 | defer task.End()
94 |
95 | // 材料
96 | water := amountCoffee.Water()
97 | beans := amountCoffee.Beans()
98 |
99 | fmt.Println(water)
100 | fmt.Println(beans)
101 |
102 | var wg sync.WaitGroup
103 |
104 | // お湯を沸かす
105 | var hotWater HotWater
106 | var hwmu sync.Mutex
107 | for water > 0 {
108 | water -= 600 * MilliLiterWater
109 | // TODO: wgに1を加える
110 | go func() {
111 | // TODO: deferでwg.Doneを仕掛ける
112 | hw := boil(ctx, 600*MilliLiterWater)
113 | hwmu.Lock()
114 | defer hwmu.Unlock()
115 | hotWater += hw
116 | }()
117 | }
118 |
119 | // 豆を挽く
120 | var groundBeans GroundBean
121 | var gbmu sync.Mutex
122 | for beans > 0 {
123 | beans -= 20 * GramBeans
124 | wg.Add(1)
125 | go func() {
126 | defer wg.Done()
127 | gb := grind(ctx, 20*GramBeans)
128 | // TODO: gbmuでロックを取る
129 | // TODO: gbmuのロックをdeferで解除する
130 | groundBeans += gb
131 | }()
132 | }
133 |
134 | wg.Wait()
135 | fmt.Println(hotWater)
136 | fmt.Println(groundBeans)
137 |
138 | // コーヒーを淹れる
139 | var wg2 sync.WaitGroup
140 | var coffee Coffee
141 | var cfmu sync.Mutex
142 | cups := 4 * CupsCoffee
143 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
144 | hotWater -= cups.HotWater()
145 | groundBeans -= cups.GroundBeans()
146 | // TODO: wg2に1加える
147 | go func() {
148 | // TODO: wg2のDoneをdeferで呼ぶ
149 | cf := brew(ctx, cups.HotWater(), cups.GroundBeans())
150 | cfmu.Lock()
151 | defer cfmu.Unlock()
152 | coffee += cf
153 | }()
154 | }
155 |
156 | // TODO: wg2を使って待ち合わせ
157 | fmt.Println(coffee)
158 | }
159 |
160 | // お湯を沸かす
161 | func boil(ctx context.Context, water Water) HotWater {
162 | defer trace.StartRegion(ctx, "boil").End()
163 | time.Sleep(400 * time.Millisecond)
164 | return HotWater(water)
165 | }
166 |
167 | // コーヒー豆を挽く
168 | func grind(ctx context.Context, beans Bean) GroundBean {
169 | defer trace.StartRegion(ctx, "grind").End()
170 | time.Sleep(200 * time.Millisecond)
171 | return GroundBean(beans)
172 | }
173 |
174 | // コーヒーを淹れる
175 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) Coffee {
176 | defer trace.StartRegion(ctx, "brew").End()
177 | time.Sleep(1 * time.Second)
178 | // 少ない方を優先する
179 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
180 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
181 | if cups1 < cups2 {
182 | return cups1
183 | }
184 | return cups2
185 | }
186 |
--------------------------------------------------------------------------------
/skeleton/step04/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/skeleton/step04/trace.png
--------------------------------------------------------------------------------
/skeleton/step05/README.md:
--------------------------------------------------------------------------------
1 | # STEP 5: ゴールーチンとエラー処理
2 |
3 | ## エラー処理
4 |
5 | プログラムにはエラー処理はつきものです。
6 | ファイルが開けなかったり、ネットワークに接続できなかったりする可能性があります。
7 | 予想可能なエラーは適切に処理するようにプログラムを組む必要があります。
8 | コーヒーを淹れるプログラムでも、水が多すぎたり、豆が多すぎたり、お湯が少なすぎたりするかもしれません。
9 |
10 | Goでは、エラーは`error`型で表されます。
11 | エラーがあるかないかは、次のように`if`で比較することが多いでしょう。
12 |
13 | ```go
14 | err := f()
15 | if err != nil {
16 | // エラー処理
17 | }
18 |
19 | // これでもOK
20 | if err := f(); err != nil {
21 | // エラー処理
22 | }
23 | ```
24 |
25 | 受け取ったエラーはログに吐いたり、情報を付加して呼び出し元にreturnしたりします。
26 | `panic`を起こしたり、無視することは極力さけるようにしましょう。
27 |
28 | ## ゴールーチンとエラー処理
29 |
30 | 別のゴールーチンで行っている処理でエラーが発生する場合はどのようにハンドリングすればよいのでしょうか。
31 | エラーも値なのでチャネルを使って伝搬する方法が考えられます。
32 |
33 | しかし、単純な場合はチャネルでも問題ありませんが、`sync.WaitGroup`などを使っているような複雑な場合については、チャンネルを用いると逆に煩雑になってしまう恐れがあります。
34 | `golang.org/x/sync/errgroup`という準標準なパッケージを使うことで、このようなパターンをうまくハンドリングすることができます。
35 |
36 | `errgroup`パッケージには、`sync.WaitGroup`に似た`errgroup.Group`が提供されています。
37 | `sync.Wait`の`Add`メソッドや`Done`メソッドの代わりに、`Go`メソッドがあります。
38 | `Go`メソッドは引数で渡した関数をゴールーチンで実行してくれます。
39 | この関数の戻り値はerror型でゴールーチンでエラーが発生した場合には適切に処理ができるようになっています。
40 |
41 | `Wait`メソッドを実行すると、`Go`メソッドで呼び出された関数がすべて終了するまで処理がブロックされます。
42 | もし、1つでもエラーを返す関数があれば、`Wait`メソッドは戻り値で最初に発生したエラーを返します。
43 |
44 | ```go
45 | var eg errgroup.Group
46 |
47 | eg.Go(func() error {
48 | if err := f(); err != nil {
49 | return err
50 | }
51 | })
52 |
53 | eg.Go(func() error {
54 | if err := g(); err != nil {
55 | return err
56 | }
57 | })
58 |
59 | if err := eg.Wait(); err != nil {
60 | // エラー処理
61 | }
62 | ```
63 |
64 | ## プログラムの改造
65 |
66 | `boil`関数、`grind`関数、`brew`関数がエラーを返すようになっています。
67 | `sync.WaitGroup`の代わりに`errgroup.Group`を使うことでエラーハンドリングをできるようにしましょう。
68 |
69 | ## 実行
70 |
71 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。
72 |
73 | ```
74 | $ go get -u golang.org/x/sync/errgroup
75 | ```
76 |
77 | 次のコマンドで実行することができます。
78 |
79 | ```
80 | $ go run main.go
81 | ```
82 |
83 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。
84 |
--------------------------------------------------------------------------------
/skeleton/step05/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step05
2 |
3 | go 1.14
4 |
5 | require golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
6 |
--------------------------------------------------------------------------------
/skeleton/step05/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
2 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3 |
--------------------------------------------------------------------------------
/skeleton/step05/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "os"
9 | "runtime/trace"
10 | "sync"
11 | "time"
12 |
13 | "golang.org/x/sync/errgroup"
14 | )
15 |
16 | type (
17 | Bean int
18 | GroundBean int
19 | Water int
20 | HotWater int
21 | Coffee int
22 | )
23 |
24 | const (
25 | GramBeans Bean = 1
26 | GramGroundBeans GroundBean = 1
27 | MilliLiterWater Water = 1
28 | MilliLiterHotWater HotWater = 1
29 | CupsCoffee Coffee = 1
30 | )
31 |
32 | func (w Water) String() string {
33 | return fmt.Sprintf("%d[ml] water", int(w))
34 | }
35 |
36 | func (hw HotWater) String() string {
37 | return fmt.Sprintf("%d[ml] hot water", int(hw))
38 | }
39 |
40 | func (b Bean) String() string {
41 | return fmt.Sprintf("%d[g] beans", int(b))
42 | }
43 |
44 | func (gb GroundBean) String() string {
45 | return fmt.Sprintf("%d[g] ground beans", int(gb))
46 | }
47 |
48 | func (cups Coffee) String() string {
49 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
50 | }
51 |
52 | // 1カップのコーヒーを淹れるのに必要な水の量
53 | func (cups Coffee) Water() Water {
54 | return Water(180*cups) / MilliLiterWater
55 | }
56 |
57 | // 1カップのコーヒーを淹れるのに必要なお湯の量
58 | func (cups Coffee) HotWater() HotWater {
59 | return HotWater(180*cups) / MilliLiterHotWater
60 | }
61 |
62 | // 1カップのコーヒーを淹れるのに必要な豆の量
63 | func (cups Coffee) Beans() Bean {
64 | return Bean(20*cups) / GramBeans
65 | }
66 |
67 | // 1カップのコーヒーを淹れるのに必要な粉の量
68 | func (cups Coffee) GroundBeans() GroundBean {
69 | return GroundBean(20*cups) / GramGroundBeans
70 | }
71 |
72 | func main() {
73 | f, err := os.Create("trace.out")
74 | if err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | defer func() {
78 | if err := f.Close(); err != nil {
79 | log.Fatalln("Error:", err)
80 | }
81 | }()
82 |
83 | if err := trace.Start(f); err != nil {
84 | log.Fatalln("Error:", err)
85 | }
86 | defer trace.Stop()
87 |
88 | _main()
89 | }
90 |
91 | func _main() {
92 | // 作るコーヒーの数
93 | const amountCoffee = 20 * CupsCoffee
94 |
95 | ctx, task := trace.NewTask(context.Background(), "make coffe")
96 | defer task.End()
97 |
98 | // 材料
99 | water := amountCoffee.Water()
100 | beans := amountCoffee.Beans()
101 |
102 | fmt.Println(water)
103 | fmt.Println(beans)
104 |
105 | var eg errgroup.Group
106 |
107 | // お湯を沸かす
108 | var hotWater HotWater
109 | var hwmu sync.Mutex
110 | for water > 0 {
111 | water -= 600 * MilliLiterWater
112 | // TODO: egのGoメソッドで関数として呼び出す
113 | hw, err := boil(ctx, 600*MilliLiterWater)
114 | if err != nil {
115 | // TODO: エラーを返す
116 | }
117 | hwmu.Lock()
118 | defer hwmu.Unlock()
119 | hotWater += hw
120 | // TODO: エラーが起きなかった場合はnilを返す
121 | // ここまで関数にする
122 | }
123 |
124 | // 豆を挽く
125 | var groundBeans GroundBean
126 | var gbmu sync.Mutex
127 | for beans > 0 {
128 | beans -= 20 * GramBeans
129 | eg.Go(func() error {
130 | gb, err := grind(ctx, 20*GramBeans)
131 | if err != nil {
132 | return err
133 | }
134 | gbmu.Lock()
135 | defer gbmu.Unlock()
136 | groundBeans += gb
137 | return nil
138 | })
139 | }
140 |
141 | if err := eg.Wait(); err != nil {
142 | fmt.Fprintln(os.Stderr, "Error:", err)
143 | return
144 | }
145 | fmt.Println(hotWater)
146 | fmt.Println(groundBeans)
147 |
148 | // コーヒーを淹れる
149 | var eg2 errgroup.Group
150 | var coffee Coffee
151 | var cfmu sync.Mutex
152 | cups := 4 * CupsCoffee
153 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
154 | hotWater -= cups.HotWater()
155 | groundBeans -= cups.GroundBeans()
156 | eg2.Go(func() error {
157 | cf, err := brew(ctx, cups.HotWater(), cups.GroundBeans())
158 | if err != nil {
159 | return err
160 | }
161 | cfmu.Lock()
162 | defer cfmu.Unlock()
163 | coffee += cf
164 | return nil
165 | })
166 | }
167 |
168 | // TODO: eg2のWaitで待ち合わせを行う。
169 | // エラーが発生した場合はエラーをos.Stderrに出力する。
170 | // returnで_main関数を終了する。
171 |
172 | fmt.Println(coffee)
173 | }
174 |
175 | // お湯を沸かす
176 | func boil(ctx context.Context, water Water) (HotWater, error) {
177 | defer trace.StartRegion(ctx, "boil").End()
178 | if water > 600*MilliLiterWater {
179 | return 0, errors.New("1度に沸かすことのできるお湯は600[ml]までです")
180 | }
181 | time.Sleep(400 * time.Millisecond)
182 | return HotWater(water), nil
183 | }
184 |
185 | // コーヒー豆を挽く
186 | func grind(ctx context.Context, beans Bean) (GroundBean, error) {
187 | defer trace.StartRegion(ctx, "grind").End()
188 | if beans > 20*GramBeans {
189 | return 0, errors.New("1度に挽くことのできる豆は20[g]までです")
190 | }
191 | time.Sleep(200 * time.Millisecond)
192 | return GroundBean(beans), nil
193 | }
194 |
195 | // コーヒーを淹れる
196 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) (Coffee, error) {
197 | defer trace.StartRegion(ctx, "brew").End()
198 |
199 | if hotWater < (1 * CupsCoffee).HotWater() {
200 | return 0, errors.New("お湯が足りません")
201 | }
202 |
203 | if groundBeans < (1 * CupsCoffee).GroundBeans() {
204 | return 0, errors.New("粉が足りません")
205 | }
206 |
207 | time.Sleep(1 * time.Second)
208 | // 少ない方を優先する
209 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
210 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
211 | if cups1 < cups2 {
212 | return cups1, nil
213 | }
214 | return cups2, nil
215 | }
216 |
--------------------------------------------------------------------------------
/skeleton/step06/README.md:
--------------------------------------------------------------------------------
1 | # STEP 6: コンテキストとキャンセル処理
2 |
3 | ## キャンセル処理
4 |
5 | とあるゴールーチンでエラーが発生した場合に、
6 | 他のゴールーチンの処理をキャンセルしたい場合があります。
7 |
8 | Goでは、ゴールーチンのキャンセル処理のために`context.Context`を用います。
9 | `context.WithCancel`関数でラップしたコンテキストは第2戻り値返されたキャンセル用の関数が呼び出されるか、親のコンテキストがキャンセルされるとキャンセルされます。
10 | キャンセルされたことを知るには、`context.Context`の`Done`メソッドから返ってくるチャネルを用います。
11 |
12 | 次の例では、2つのゴールーチンを立ち上げ、キャンセルを`Done`メソッドのチャネルで伝えています。
13 | `select`は複数のチャンネルへの送受信を待機することのできる構文で、どのケースのチャネルも反応しない場合は、`default`が実行されます。
14 |
15 | ```go
16 | func main() {
17 | root := context.Background()
18 | ctx1, cancel := context.WithCancel(root)
19 | ctx2, _ := context.WithCancel(ctx1)
20 |
21 | var wg sync.WaitGroup
22 | wg.Add(2) // 2つのゴールーチンが終わるの待つため
23 |
24 | go func() {
25 | defer wg.Done()
26 | for {
27 | select {
28 | case <-ctx2.Done():
29 | fmt.Println("cancel goroutine1")
30 | return
31 | default:
32 | fmt.Println("waint goroutine1")
33 | time.Sleep(500 * time.Millisecond)
34 | }
35 | }
36 | }()
37 |
38 | go func() {
39 | defer wg.Done()
40 | for {
41 | select {
42 | case <-ctx2.Done():
43 | fmt.Println("cancel goroutine2")
44 | return
45 | default:
46 | fmt.Println("waint goroutine2")
47 | time.Sleep(500 * time.Millisecond)
48 | }
49 | }
50 | }()
51 |
52 | time.Sleep(2 * time.Second)
53 | cancel()
54 | wg.Wait()
55 | }
56 | ```
57 |
58 | ## errgroup.Groupを使ったキャンセル処理
59 |
60 | `errgroup.Group`は`errgroup.WithContext`を用いることで、エラーが起きた際に処理をキャンセルすることができます。
61 |
62 | ```go
63 | func main() {
64 | root := context.Background()
65 | eg, ctx := errgroup.WithContext(root)
66 |
67 | eg.Go(func() error {
68 | for {
69 | select {
70 | case <-ctx.Done():
71 | fmt.Println("cancel goroutine1")
72 | return nil
73 | default:
74 | fmt.Println("waint goroutine1")
75 | time.Sleep(500 * time.Millisecond)
76 | }
77 | }
78 | })
79 |
80 | eg.Go(func() error {
81 | time.Sleep(2 * time.Second)
82 | return errors.New("error")
83 | })
84 |
85 | if err := eg.Wait(); err != nil {
86 | log.Fatal(err)
87 | }
88 | }
89 | ```
90 |
91 | ## プログラムの改造
92 |
93 | `errgroup.WithContext`を用いてエラーが発生した場合のキャンセル処理をハンドリングしましょう。
94 |
95 | ## 実行
96 |
97 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。
98 |
99 | ```
100 | $ go get -u golang.org/x/sync/errgroup
101 | ```
102 |
103 | 次のコマンドで実行することができます。
104 |
105 | ```
106 | $ go run main.go
107 | ```
108 |
109 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。
110 |
--------------------------------------------------------------------------------
/skeleton/step06/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step06
2 |
3 | go 1.14
4 |
5 | require golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
6 |
--------------------------------------------------------------------------------
/skeleton/step06/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
2 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3 |
--------------------------------------------------------------------------------
/skeleton/step06/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "os"
9 | "runtime/trace"
10 | "sync"
11 | "time"
12 |
13 | "golang.org/x/sync/errgroup"
14 | )
15 |
16 | type (
17 | Bean int
18 | GroundBean int
19 | Water int
20 | HotWater int
21 | Coffee int
22 | )
23 |
24 | const (
25 | GramBeans Bean = 1
26 | GramGroundBeans GroundBean = 1
27 | MilliLiterWater Water = 1
28 | MilliLiterHotWater HotWater = 1
29 | CupsCoffee Coffee = 1
30 | )
31 |
32 | func (w Water) String() string {
33 | return fmt.Sprintf("%d[ml] water", int(w))
34 | }
35 |
36 | func (hw HotWater) String() string {
37 | return fmt.Sprintf("%d[ml] hot water", int(hw))
38 | }
39 |
40 | func (b Bean) String() string {
41 | return fmt.Sprintf("%d[g] beans", int(b))
42 | }
43 |
44 | func (gb GroundBean) String() string {
45 | return fmt.Sprintf("%d[g] ground beans", int(gb))
46 | }
47 |
48 | func (cups Coffee) String() string {
49 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
50 | }
51 |
52 | // 1カップのコーヒーを淹れるのに必要な水の量
53 | func (cups Coffee) Water() Water {
54 | return Water(180*cups) / MilliLiterWater
55 | }
56 |
57 | // 1カップのコーヒーを淹れるのに必要なお湯の量
58 | func (cups Coffee) HotWater() HotWater {
59 | return HotWater(180*cups) / MilliLiterHotWater
60 | }
61 |
62 | // 1カップのコーヒーを淹れるのに必要な豆の量
63 | func (cups Coffee) Beans() Bean {
64 | return Bean(20*cups) / GramBeans
65 | }
66 |
67 | // 1カップのコーヒーを淹れるのに必要な粉の量
68 | func (cups Coffee) GroundBeans() GroundBean {
69 | return GroundBean(20*cups) / GramGroundBeans
70 | }
71 |
72 | func main() {
73 | f, err := os.Create("trace.out")
74 | if err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | defer func() {
78 | if err := f.Close(); err != nil {
79 | log.Fatalln("Error:", err)
80 | }
81 | }()
82 |
83 | if err := trace.Start(f); err != nil {
84 | log.Fatalln("Error:", err)
85 | }
86 | defer trace.Stop()
87 |
88 | _main()
89 | }
90 |
91 | func _main() {
92 | // 作るコーヒーの数
93 | const amountCoffee = 20 * CupsCoffee
94 |
95 | taskCtx, task := trace.NewTask(context.Background(), "make coffe")
96 | defer task.End()
97 |
98 | // 材料
99 | water := amountCoffee.Water()
100 | beans := amountCoffee.Beans()
101 |
102 | fmt.Println(water)
103 | fmt.Println(beans)
104 |
105 | // TODO: taskCtxをベースにしてerrgroup.WithContextで
106 | // errgroup.Groupとコンテキストを作成する。
107 |
108 | // お湯を沸かす
109 | var hotWater HotWater
110 | var hwmu sync.Mutex
111 | for water > 0 {
112 | water -= 600 * MilliLiterWater
113 | eg.Go(func() error {
114 | select {
115 | case <-ctx.Done():
116 | trace.Log(ctx, "boil error", ctx.Err().Error())
117 | return ctx.Err()
118 | default:
119 | }
120 | hw, err := boil(ctx, 600*MilliLiterWater)
121 | if err != nil {
122 | return err
123 | }
124 | hwmu.Lock()
125 | defer hwmu.Unlock()
126 | hotWater += hw
127 | return nil
128 | })
129 | }
130 |
131 | // 豆を挽く
132 | var groundBeans GroundBean
133 | var gbmu sync.Mutex
134 | for beans > 0 {
135 | beans -= 20 * GramBeans
136 | eg.Go(func() error {
137 | // TODO: キャンセルを検出する
138 |
139 | gb, err := grind(ctx, 20*GramBeans)
140 | if err != nil {
141 | return err
142 | }
143 | gbmu.Lock()
144 | defer gbmu.Unlock()
145 | groundBeans += gb
146 | return nil
147 | })
148 | }
149 |
150 | if err := eg.Wait(); err != nil {
151 | fmt.Fprintln(os.Stderr, "Error:", err)
152 | return
153 | }
154 | fmt.Println(hotWater)
155 | fmt.Println(groundBeans)
156 |
157 | // コーヒーを淹れる
158 | eg, ctx = errgroup.WithContext(taskCtx)
159 | var coffee Coffee
160 | var cfmu sync.Mutex
161 | cups := 4 * CupsCoffee
162 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
163 | hotWater -= cups.HotWater()
164 | groundBeans -= cups.GroundBeans()
165 | eg.Go(func() error {
166 | select {
167 | case <-ctx.Done():
168 | trace.Log(ctx, "brew error", ctx.Err().Error())
169 | return ctx.Err()
170 | default:
171 | }
172 |
173 | cf, err := brew(ctx, cups.HotWater(), cups.GroundBeans())
174 | if err != nil {
175 | return err
176 | }
177 | cfmu.Lock()
178 | defer cfmu.Unlock()
179 | coffee += cf
180 | return nil
181 | })
182 | }
183 |
184 | if err := eg.Wait(); err != nil {
185 | fmt.Fprintln(os.Stderr, "Error:", err)
186 | return
187 | }
188 | fmt.Println(coffee)
189 | }
190 |
191 | // お湯を沸かす
192 | func boil(ctx context.Context, water Water) (HotWater, error) {
193 | defer trace.StartRegion(ctx, "boil").End()
194 | if water > 600*MilliLiterWater {
195 | return 0, errors.New("1度に沸かすことのできるお湯は600[ml]までです")
196 | }
197 | time.Sleep(400 * time.Millisecond)
198 | return HotWater(water), nil
199 | }
200 |
201 | // コーヒー豆を挽く
202 | func grind(ctx context.Context, beans Bean) (GroundBean, error) {
203 | defer trace.StartRegion(ctx, "grind").End()
204 | if beans > 20*GramBeans {
205 | return 0, errors.New("1度に挽くことのできる豆は20[g]までです")
206 | }
207 | time.Sleep(200 * time.Millisecond)
208 | return GroundBean(beans), nil
209 | }
210 |
211 | // コーヒーを淹れる
212 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) (Coffee, error) {
213 | defer trace.StartRegion(ctx, "brew").End()
214 |
215 | if hotWater < (1 * CupsCoffee).HotWater() {
216 | return 0, errors.New("お湯が足りません")
217 | }
218 |
219 | if groundBeans < (1 * CupsCoffee).GroundBeans() {
220 | return 0, errors.New("粉が足りません")
221 | }
222 |
223 | time.Sleep(1 * time.Second)
224 | // 少ない方を優先する
225 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
226 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
227 | if cups1 < cups2 {
228 | return cups1, nil
229 | }
230 | return cups2, nil
231 | }
232 |
--------------------------------------------------------------------------------
/solution/step01/README.md:
--------------------------------------------------------------------------------
1 | # STEP 1: ゴールーチンを使わずに処理する
2 |
3 | ## コーヒーを淹れるプログラムを作ろう
4 |
5 | このハンズオンでは、題材としてコーヒーを淹れるプログラムを作ります。
6 | 実際にはコーヒーを淹れるわけではありませんが、一連の手順をいかに並行処理で効率化していくかということを学ぶことができます。
7 |
8 | コーヒーを淹れるには次の手順が必要でしょう。
9 |
10 | * お湯を沸かす
11 | * 豆を挽く
12 | * コーヒーを淹れる
13 |
14 | ## データ型を定義しよう
15 |
16 | コーヒーを淹れるには、お湯と挽かれたコーヒー豆の粉が必要になります。
17 | お湯を沸かすには水が必要になり、コーヒー豆の粉を手に入れるには、コーヒー豆が必要です。
18 | つまり、各手順で次のデータの変換が行われます。
19 |
20 | * お湯を沸かす: 水 -> お湯
21 | * 豆を挽く: コーヒー豆 -> 挽かれたコーヒー豆の粉
22 | * コーヒーを淹れる: お湯, 挽かれたコーヒー豆の粉 -> コーヒー
23 |
24 | このプログラムには、水、お湯、豆、挽かれた豆、コーヒーの5つの種類のデータが存在することがわかります。
25 | これらのデータを表すために、データ型を作成しましょう。
26 | データ型は`type`を用いて定義することができます。
27 |
28 | ```go
29 | type (
30 | Bean int // 豆
31 | GroundBean int // 挽かれた豆
32 | Water int // 水
33 | HotWater int // お湯
34 | Coffee int // コーヒー
35 | )
36 | ```
37 |
38 | また、利便性のために次のようにいくつか定数も用意しておきます。
39 | こうすることで、`10 * GramBeans`のように記述することができます。
40 |
41 | ```go
42 | const (
43 | GramBeans Bean = 1
44 | GramGroundBeans GroundBean = 1
45 | MilliLiterWater Water = 1
46 | MilliLiterHotWater HotWater = 1
47 | CupsCoffee Coffee = 1
48 | )
49 | ```
50 |
51 | つぎに、N杯のコーヒーを淹れるために必要な材料の分量を返すメソッドを用意します。
52 | メソッドは、`Coffee`型のメソッドとして設けます。
53 | こうすることで、2杯のコーヒーに必要な水の分量を`(2 * Cupscoffee).Water()`のように取得することができます。
54 |
55 | ```go
56 | // Nカップのコーヒーを淹れるのに必要な水の量
57 | func (cups Coffee) Water() Water {
58 | return Water(180*cups) / MilliLiterWater
59 | }
60 |
61 | // Nカップのコーヒーを淹れるのに必要なお湯の量
62 | func (cups Coffee) HotWater() HotWater {
63 | return HotWater(180*cups) / MilliLiterHotWater
64 | }
65 |
66 | // Nカップのコーヒーを淹れるのに必要な豆の量
67 | func (cups Coffee) Beans() Bean {
68 | return Bean(20*cups) / GramBeans
69 | }
70 |
71 | // Nカップのコーヒーを淹れるのに必要な粉の量
72 | func (cups Coffee) GroundBeans() GroundBean {
73 | return GroundBean(20*cups) / GramGroundBeans
74 | }
75 | ```
76 |
77 | ## お湯を沸かす
78 |
79 | お湯を沸かす関数`boil`を作成します。
80 | `boil`は一定時間立つと、引数で与えた分量と同じ量のお湯を返します。
81 |
82 | ```go
83 | // お湯を沸かす
84 | func boil(water Water) HotWater {
85 | time.Sleep(400 * time.Millisecond)
86 | return HotWater(water)
87 | }
88 | ```
89 |
90 | ## コーヒー豆を挽く
91 |
92 | コーヒー豆を挽く関数`grind`を作ります。
93 | `grind`は引数にコーヒー豆を受け取り、一定時間後に挽いた豆を返します。
94 |
95 | ```go
96 | // コーヒー豆を挽く
97 | func grind(beans Bean) GroundBean {
98 | time.Sleep(200 * time.Millisecond)
99 | return GroundBean(beans)
100 | }
101 | ```
102 |
103 | ## コーヒーを淹れる
104 |
105 | コーヒーを淹れる関数`brew`を作ります。
106 | `brew`は引数にお湯と挽いた豆を受け取り、一定時間後にコーヒーを返します。
107 |
108 | ```go
109 | // コーヒーを淹れる
110 | func brew(hotWater HotWater, groundBeans GroundBean) Coffee {
111 | time.Sleep(1 * time.Second)
112 | // 少ない方を優先する
113 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
114 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
115 | if cups1 < cups2 {
116 | return cups1
117 | }
118 | return cups2
119 | }
120 | ```
121 |
122 | ## 処理をまとめる
123 |
124 | `main`関数から、`boil`関数、`grind`関数、`brew`関数を順に呼び、コーヒーを淹れます。
125 | 材料は次のように20杯分のコーヒーとしてます。
126 |
127 | ```go
128 | // 作るコーヒーの数
129 | const amountCoffee = 20 * CupsCoffee
130 |
131 | // 材料
132 | water := amountCoffee.Water()
133 | beans := amountCoffee.Beans()
134 | ```
135 |
136 | 一度に沸かせるお湯の量や挽ける豆の量、淹れれるコーヒーの量は次のように決まっているものとします。
137 |
138 | * 一度に沸かせるお湯の量: 600[ml]
139 | * 一度に挽ける豆の量: 20[g]
140 | * 一度に淹れれるコーヒー: 4杯
141 |
142 | `boil`関数、`grind`関数、`brew`関数を複数回呼び出すことで、20杯のコーヒーを淹れることができます。
143 |
144 | ## 実行
145 |
146 | `TODO`を埋めると次のコマンドで実行することができます。
147 |
148 | ```
149 | $ go run main.go
150 | ```
151 |
152 | 次のように表示されれば成功です。
153 |
154 | ```
155 | 3600[ml] water
156 | 400[g] beans
157 | 3600[ml] hot water
158 | 400[g] ground beans
159 | 20 cup(s) coffee
160 | ```
161 |
--------------------------------------------------------------------------------
/solution/step01/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step01
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/solution/step01/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | type (
9 | Bean int
10 | GroundBean int
11 | Water int
12 | HotWater int
13 | Coffee int
14 | )
15 |
16 | const (
17 | GramBeans Bean = 1
18 | GramGroundBeans GroundBean = 1
19 | MilliLiterWater Water = 1
20 | MilliLiterHotWater HotWater = 1
21 | CupsCoffee Coffee = 1
22 | )
23 |
24 | func (w Water) String() string {
25 | return fmt.Sprintf("%d[ml] water", int(w))
26 | }
27 |
28 | func (hw HotWater) String() string {
29 | return fmt.Sprintf("%d[ml] hot water", int(hw))
30 | }
31 |
32 | func (b Bean) String() string {
33 | return fmt.Sprintf("%d[g] beans", int(b))
34 | }
35 |
36 | func (gb GroundBean) String() string {
37 | return fmt.Sprintf("%d[g] ground beans", int(gb))
38 | }
39 |
40 | func (cups Coffee) String() string {
41 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
42 | }
43 |
44 | // 1カップのコーヒーを淹れるのに必要な水の量
45 | func (cups Coffee) Water() Water {
46 | return Water(180*cups) / MilliLiterWater
47 | }
48 |
49 | // 1カップのコーヒーを淹れるのに必要なお湯の量
50 | func (cups Coffee) HotWater() HotWater {
51 | return HotWater(180*cups) / MilliLiterHotWater
52 | }
53 |
54 | // 1カップのコーヒーを淹れるのに必要な豆の量
55 | func (cups Coffee) Beans() Bean {
56 | return Bean(20*cups) / GramBeans
57 | }
58 |
59 | // 1カップのコーヒーを淹れるのに必要な粉の量
60 | func (cups Coffee) GroundBeans() GroundBean {
61 | return GroundBean(20*cups) / GramGroundBeans
62 | }
63 |
64 | // お湯を沸かす
65 | func boil(water Water) HotWater {
66 | time.Sleep(400 * time.Millisecond)
67 | return HotWater(water)
68 | }
69 |
70 | // コーヒー豆を挽く
71 | func grind(beans Bean) GroundBean {
72 | time.Sleep(200 * time.Millisecond)
73 | return GroundBean(beans)
74 | }
75 |
76 | // コーヒーを淹れる
77 | func brew(hotWater HotWater, groundBeans GroundBean) Coffee {
78 | time.Sleep(1 * time.Second)
79 | // 少ない方を優先する
80 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
81 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
82 | if cups1 < cups2 {
83 | return cups1
84 | }
85 | return cups2
86 | }
87 |
88 | func main() {
89 | // 作るコーヒーの数
90 | const amountCoffee = 20 * CupsCoffee
91 |
92 | // 材料
93 | water := amountCoffee.Water()
94 | beans := amountCoffee.Beans()
95 |
96 | fmt.Println(water)
97 | fmt.Println(beans)
98 |
99 | // お湯を沸かす
100 | var hotWater HotWater
101 | for water > 0 {
102 | water -= 600 * MilliLiterWater
103 | hotWater += boil(600 * MilliLiterWater)
104 | }
105 | fmt.Println(hotWater)
106 |
107 | // 豆を挽く
108 | var groundBeans GroundBean
109 | for beans > 0 {
110 | beans -= 20 * GramBeans
111 | groundBeans += grind(20 * GramBeans)
112 | }
113 | fmt.Println(groundBeans)
114 |
115 | // コーヒーを淹れる
116 | var coffee Coffee
117 | cups := 4 * CupsCoffee
118 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
119 | hotWater -= cups.HotWater()
120 | groundBeans -= cups.GroundBeans()
121 | coffee += brew(cups.HotWater(), cups.GroundBeans())
122 | }
123 |
124 | fmt.Println(coffee)
125 | }
126 |
--------------------------------------------------------------------------------
/solution/step02/README.md:
--------------------------------------------------------------------------------
1 | # STEP 2: ボトルネックを探す
2 |
3 | ## トレースを行う
4 |
5 | STEP 1で作成したプログラムはなんとなく遅い気がします。
6 | STEP 2では、`runtime/trace`パッケージを用いてトレースし、ボトルネックとなる部分を探します。
7 |
8 | まずは、`main`関数をトレースするために、`main`関数の中身を`_main`関数に移動させます。
9 |
10 | そして、トレース対象の処理を開始する前に、`trace.Start`関数を呼び出し、終了後に`trace.Stop`を呼び出します。
11 | なお、`defer`を用いることで、関数の終了時に呼び出すことができます。
12 |
13 | ```go
14 | func main() {
15 | f, err := os.Create("trace.out")
16 | if err != nil {
17 | log.Fatalln("Error:", err)
18 | }
19 | defer func() {
20 | if err := f.Close(); err != nil {
21 | log.Fatalln("Error:", err)
22 | }
23 | }()
24 |
25 | if err := trace.Start(f); err != nil {
26 | log.Fatalln("Error:", err)
27 | }
28 | defer trace.Stop()
29 |
30 | _main()
31 | }
32 | ```
33 |
34 | ## ユーザアノテーション
35 |
36 | Go1.11からユーザアノテーションという機能が入りました。
37 | ユーザが任意のタイミングでトレース情報を出力することができる機能です。
38 |
39 | ユーザアノテーションは、以下の単位で行うことができます。
40 |
41 | * Task
42 | * Region
43 | * Log
44 |
45 | 1つのTaskに複数のRegionがあり、Regionの中でLogを出すというイメージです。
46 |
47 | ここでは、Taskは「コーヒーを淹れること」、Regionは「お湯を沸かす」、「豆を挽く」、「コーヒー淹れる」の3つになります。
48 |
49 | `Task`は次のように作成することができます。
50 | 作成した`Task`は、`End`メソッドを呼び出すまでの処理が対象となります。
51 | 通常は`defer`で呼び出すことで関数の終了時に呼び出します。
52 |
53 | ```go
54 | ctx, task := trace.NewTask(context.Background(), "make coffee")
55 | defer task.End()
56 | ```
57 |
58 | `trace.NewTask`関数の第1引数で返される`context.Context`型の値には、Taskの情報が保持されています。
59 |
60 | Regionは次のように、`trace.StartRegion`を呼び出すことで作ることができます。
61 | `trace.StartRegion`の第1引数に`trace.NewTask`関数で返ってきた`context.Context`を渡すことで、TaskとRegionを紐付けることができます。
62 |
63 | 作成したRegionは、`End`メソッドを呼び出すまでの処理が対象となります。
64 |
65 | ```go
66 | region := trace.StartRegion(ctx, "region_name")
67 | defer region.End()
68 | ```
69 |
70 | なお、次のように1行で書くこともできます。
71 |
72 | ```go
73 | defer trace.StartRegion(ctx, "region_name").End()
74 | ```
75 |
76 | ## 実行とトレースデータの表示
77 |
78 | `TODO`を埋めると次のコマンドで実行することができます。
79 |
80 | ```
81 | $ go run main.go
82 | ```
83 |
84 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
85 |
86 | ```
87 | $ go tool trace trace.out
88 | ```
89 |
90 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
91 |
92 |
93 |
94 | 図を見ると、`boil`、`grind`、`brew`が直列に処理されていることがわかります。
95 | `boil`と`grind`は同時に行っても問題ないので、ここを改善すれば早くなりそうです。
96 |
--------------------------------------------------------------------------------
/solution/step02/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step02
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/solution/step02/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "time"
10 | )
11 |
12 | type (
13 | Bean int
14 | GroundBean int
15 | Water int
16 | HotWater int
17 | Coffee int
18 | )
19 |
20 | const (
21 | GramBeans Bean = 1
22 | GramGroundBeans GroundBean = 1
23 | MilliLiterWater Water = 1
24 | MilliLiterHotWater HotWater = 1
25 | CupsCoffee Coffee = 1
26 | )
27 |
28 | func (w Water) String() string {
29 | return fmt.Sprintf("%d[ml] water", int(w))
30 | }
31 |
32 | func (hw HotWater) String() string {
33 | return fmt.Sprintf("%d[ml] hot water", int(hw))
34 | }
35 |
36 | func (b Bean) String() string {
37 | return fmt.Sprintf("%d[g] beans", int(b))
38 | }
39 |
40 | func (gb GroundBean) String() string {
41 | return fmt.Sprintf("%d[g] ground beans", int(gb))
42 | }
43 |
44 | func (cups Coffee) String() string {
45 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
46 | }
47 |
48 | // 1カップのコーヒーを淹れるのに必要な水の量
49 | func (cups Coffee) Water() Water {
50 | return Water(180*cups) / MilliLiterWater
51 | }
52 |
53 | // 1カップのコーヒーを淹れるのに必要なお湯の量
54 | func (cups Coffee) HotWater() HotWater {
55 | return HotWater(180*cups) / MilliLiterHotWater
56 | }
57 |
58 | // 1カップのコーヒーを淹れるのに必要な豆の量
59 | func (cups Coffee) Beans() Bean {
60 | return Bean(20*cups) / GramBeans
61 | }
62 |
63 | // 1カップのコーヒーを淹れるのに必要な粉の量
64 | func (cups Coffee) GroundBeans() GroundBean {
65 | return GroundBean(20*cups) / GramGroundBeans
66 | }
67 |
68 | func main() {
69 | f, err := os.Create("trace.out")
70 | if err != nil {
71 | log.Fatalln("Error:", err)
72 | }
73 | defer func() {
74 | if err := f.Close(); err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | }()
78 |
79 | if err := trace.Start(f); err != nil {
80 | log.Fatalln("Error:", err)
81 | }
82 | defer trace.Stop()
83 |
84 | _main()
85 | }
86 |
87 | func _main() {
88 | // 作るコーヒーの数
89 | const amountCoffee = 20 * CupsCoffee
90 |
91 | ctx, task := trace.NewTask(context.Background(), "make coffee")
92 | defer task.End()
93 |
94 | // 材料
95 | water := amountCoffee.Water()
96 | beans := amountCoffee.Beans()
97 |
98 | fmt.Println(water)
99 | fmt.Println(beans)
100 |
101 | // お湯を沸かす
102 | var hotWater HotWater
103 | for water > 0 {
104 | water -= 600 * MilliLiterWater
105 | hotWater += boil(ctx, 600*MilliLiterWater)
106 | }
107 | fmt.Println(hotWater)
108 |
109 | // 豆を挽く
110 | var groundBeans GroundBean
111 | for beans > 0 {
112 | beans -= 20 * GramBeans
113 | groundBeans += grind(ctx, 20*GramBeans)
114 | }
115 | fmt.Println(groundBeans)
116 |
117 | // コーヒーを淹れる
118 | var coffee Coffee
119 | cups := 4 * CupsCoffee
120 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
121 | hotWater -= cups.HotWater()
122 | groundBeans -= cups.GroundBeans()
123 | coffee += brew(ctx, cups.HotWater(), cups.GroundBeans())
124 | }
125 |
126 | fmt.Println(coffee)
127 | }
128 |
129 | // お湯を沸かす
130 | func boil(ctx context.Context, water Water) HotWater {
131 | defer trace.StartRegion(ctx, "boil").End()
132 | time.Sleep(400 * time.Millisecond)
133 | return HotWater(water)
134 | }
135 |
136 | // コーヒー豆を挽く
137 | func grind(ctx context.Context, beans Bean) GroundBean {
138 | defer trace.StartRegion(ctx, "grind").End()
139 | time.Sleep(200 * time.Millisecond)
140 | return GroundBean(beans)
141 | }
142 |
143 | // コーヒーを淹れる
144 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) Coffee {
145 | defer trace.StartRegion(ctx, "brew").End()
146 | time.Sleep(1 * time.Second)
147 | // 少ない方を優先する
148 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
149 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
150 | if cups1 < cups2 {
151 | return cups1
152 | }
153 | return cups2
154 | }
155 |
--------------------------------------------------------------------------------
/solution/step02/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/solution/step02/trace.png
--------------------------------------------------------------------------------
/solution/step03/README.md:
--------------------------------------------------------------------------------
1 | # STEP 3: ゴールーチンとチャネル
2 |
3 | ## ゴールーチン
4 |
5 | Goには、ゴールーチンという並行処理を行うための機能があります。
6 | 次のように、`go`というキーワードを用いて関数呼び出しを行うと新しいゴールーチンで関数を呼び出すことができます。
7 |
8 | ```go
9 | go f()
10 | ```
11 |
12 | ゴールーチンは、非常に軽量でOSのスレッドの上に複数動きます。
13 | デフォルトでは、論理CPU数と同じ数だけ並列に動くことが可能です。
14 | 複数のゴールーチンの処理をどのように切り替えるかについては、Goのランタイムがスケジューリングを行うため、ユーザは気にする必要がありません。
15 |
16 | ## チャネル
17 |
18 | ゴールーチンを用いると関数を独立して動作させることができます。
19 | ゴールーチン間のデータのやりとりはどのように行えばよいのでしょうか?
20 |
21 | 次のように、共有の変数を用いる方法はどうでしょうか。
22 |
23 | ```go
24 | func main() {
25 | var done bool
26 | go func() {
27 | // 重たい処理
28 | time.Sleep(2 * time.Second)
29 | done := true
30 | }()
31 |
32 | // 処理が終わるまで待つ
33 | for done {
34 | time.Sleep(10 * time.Millisecond)
35 | }
36 | }
37 | ```
38 |
39 | この例では、1つのゴールーチンでしか変数`done`に書き込んでいないので大きな問題は起きませんが、共有の変数に同時に読み書きが発生すると競合が起きてしまいます。
40 |
41 | そこでGoでは、ゴールーチン間のデータのやりとりにチャネルというデータ構造を用います。
42 | チャネルはゴールーチン間の通信を行うデータ型で次のように利用することができます。
43 |
44 | ```go
45 | func main() {
46 | done := make(chan bool)
47 | go func() {
48 | // 重たい処理
49 | time.Sleep(2 * time.Second)
50 | done <- true
51 | }()
52 |
53 | // 処理が終わるまで待つ
54 | <-done
55 | }
56 | ```
57 |
58 | チャネルの初期化には、組込みの`make`関数を用います。
59 | `make`関数には、`chan bool`のようなチャネルを表す型を指定します。
60 | `chan bool`は`bool`型のデータをやり取りするためのチャネルを表します。
61 |
62 | `done <- true`のように書くことでチャネルに値を送ることができます。
63 | また、`<-done`のように書くと、チャネルから値を受信することができます。
64 |
65 | チャネルの送受信ではデータが受信・送信されるまで処理がブロックされます。
66 | なお、ここでは扱いませんがチャネルに空きバッファがある場合はブロックされずに処理が進みます。
67 |
68 | チャネルは、引数や戻り値に渡すことができます。
69 | その際、`chan bool`のように型を書くと送受信可能なチャネルになってしまうため、
70 | 引数の場合には`chan<- bool`、戻り値の場合には`<-chan bool`と書くことで、受信または送信限定のチャネルとすることができます。
71 |
72 | ## 実行とトレースデータの表示
73 |
74 | `TODO`を埋めると次のコマンドで実行することができます。
75 |
76 | ```
77 | $ go run main.go
78 | ```
79 |
80 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
81 |
82 | ```
83 | $ go tool trace trace.out
84 | ```
85 |
86 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
87 |
88 |
89 |
90 | 実行時間が直列に行うより大幅に改善されていることがわかります。
91 |
92 | `boil`と`grind`が並列に処理され、その後に`brew`が処理されています。
93 | `boil`と`grind`のバーの長さが同じな理由は、`grind`の処理が終わっても`boil`の結果がすべて受け取るまでは、`grind`の結果を受け取ってもらえないためです。
94 |
--------------------------------------------------------------------------------
/solution/step03/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step03
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/solution/step03/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "time"
10 | )
11 |
12 | type (
13 | Bean int
14 | GroundBean int
15 | Water int
16 | HotWater int
17 | Coffee int
18 | )
19 |
20 | const (
21 | GramBeans Bean = 1
22 | GramGroundBeans GroundBean = 1
23 | MilliLiterWater Water = 1
24 | MilliLiterHotWater HotWater = 1
25 | CupsCoffee Coffee = 1
26 | )
27 |
28 | func (w Water) String() string {
29 | return fmt.Sprintf("%d[ml] water", int(w))
30 | }
31 |
32 | func (hw HotWater) String() string {
33 | return fmt.Sprintf("%d[ml] hot water", int(hw))
34 | }
35 |
36 | func (b Bean) String() string {
37 | return fmt.Sprintf("%d[g] beans", int(b))
38 | }
39 |
40 | func (gb GroundBean) String() string {
41 | return fmt.Sprintf("%d[g] ground beans", int(gb))
42 | }
43 |
44 | func (cups Coffee) String() string {
45 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
46 | }
47 |
48 | // 1カップのコーヒーを淹れるのに必要な水の量
49 | func (cups Coffee) Water() Water {
50 | return Water(180*cups) / MilliLiterWater
51 | }
52 |
53 | // 1カップのコーヒーを淹れるのに必要なお湯の量
54 | func (cups Coffee) HotWater() HotWater {
55 | return HotWater(180*cups) / MilliLiterHotWater
56 | }
57 |
58 | // 1カップのコーヒーを淹れるのに必要な豆の量
59 | func (cups Coffee) Beans() Bean {
60 | return Bean(20*cups) / GramBeans
61 | }
62 |
63 | // 1カップのコーヒーを淹れるのに必要な粉の量
64 | func (cups Coffee) GroundBeans() GroundBean {
65 | return GroundBean(20*cups) / GramGroundBeans
66 | }
67 |
68 | func main() {
69 | f, err := os.Create("trace.out")
70 | if err != nil {
71 | log.Fatalln("Error:", err)
72 | }
73 | defer func() {
74 | if err := f.Close(); err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | }()
78 |
79 | if err := trace.Start(f); err != nil {
80 | log.Fatalln("Error:", err)
81 | }
82 | defer trace.Stop()
83 |
84 | _main()
85 | }
86 |
87 | func _main() {
88 | // 作るコーヒーの数
89 | const amountCoffee = 20 * CupsCoffee
90 |
91 | ctx, task := trace.NewTask(context.Background(), "make coffee")
92 | defer task.End()
93 |
94 | // 材料
95 | water := amountCoffee.Water()
96 | beans := amountCoffee.Beans()
97 |
98 | fmt.Println(water)
99 | fmt.Println(beans)
100 |
101 | hwch := make(chan HotWater)
102 | gbch := make(chan GroundBean)
103 | cfch := make(chan Coffee)
104 |
105 | // お湯を沸かす
106 | var hwCount int
107 | for water > 0 {
108 | hwCount++
109 | water -= 600 * MilliLiterWater
110 | go boil(ctx, hwch, 600*MilliLiterWater)
111 | }
112 |
113 | // 豆を挽く
114 | var gbCount int
115 | for beans > 0 {
116 | beans -= 20 * GramBeans
117 | gbCount++
118 | go grind(ctx, gbch, 20*GramBeans)
119 | }
120 |
121 | var hotWater HotWater
122 | for i := 0; i < hwCount; i++ {
123 | hotWater += <-hwch
124 | }
125 | fmt.Println(hotWater)
126 |
127 | var groundBeans GroundBean
128 | for i := 0; i < gbCount; i++ {
129 | groundBeans += <-gbch
130 | }
131 | fmt.Println(groundBeans)
132 |
133 | // コーヒーを淹れる
134 | var cfCount int
135 | cups := 4 * CupsCoffee
136 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
137 | hotWater -= cups.HotWater()
138 | groundBeans -= cups.GroundBeans()
139 | cfCount++
140 | go brew(ctx, cfch, cups.HotWater(), cups.GroundBeans())
141 | }
142 |
143 | var coffee Coffee
144 | for i := 0; i < cfCount; i++ {
145 | coffee += <-cfch
146 | }
147 | fmt.Println(coffee)
148 | }
149 |
150 | // お湯を沸かす
151 | func boil(ctx context.Context, ch chan<- HotWater, water Water) {
152 | defer trace.StartRegion(ctx, "boil").End()
153 | time.Sleep(400 * time.Millisecond)
154 | ch <- HotWater(water)
155 | }
156 |
157 | // コーヒー豆を挽く
158 | func grind(ctx context.Context, ch chan<- GroundBean, beans Bean) {
159 | defer trace.StartRegion(ctx, "grind").End()
160 | time.Sleep(200 * time.Millisecond)
161 | ch <- GroundBean(beans)
162 | }
163 |
164 | // コーヒーを淹れる
165 | func brew(ctx context.Context, ch chan<- Coffee, hotWater HotWater, groundBeans GroundBean) {
166 | defer trace.StartRegion(ctx, "brew").End()
167 | time.Sleep(1 * time.Second)
168 | // 少ない方を優先する
169 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
170 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
171 | if cups1 < cups2 {
172 | ch <- cups1
173 | } else {
174 | ch <- cups2
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/solution/step03/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/solution/step03/trace.png
--------------------------------------------------------------------------------
/solution/step04/README.md:
--------------------------------------------------------------------------------
1 | # STEP 4: syncパッケージを使う
2 |
3 | ## チャネルを使わないゴールーチン間のやり取り
4 |
5 | STEP 3では、ゴールーチン間でデータのやりとりを行う場合は、
6 | データの競合を避けるためにチャネルを利用するという説明を行いました。
7 |
8 | しかし、データの競合を避けるためには、チャネルを使わずロックをとって排他制御を行う方法もあります。
9 | `sync`パッケージはゴールーチンを跨いだロックなどの便利な機能を提供するパッケージです。
10 |
11 | 例えば、次のように`sync.Mutex`を用いることでロックを取ることができます。
12 |
13 | ```go
14 | var (
15 | count int
16 | mu sync.Mutex
17 | )
18 |
19 | done := make(chan bool)
20 | go func() {
21 | for i := 0; i < 10; i++ {
22 | mu.Lock()
23 | count++
24 | mu.Unlock()
25 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
26 | }
27 | done <- true
28 | }()
29 |
30 | go func() {
31 | for i := 0; i < 10; i++ {
32 | mu.Lock()
33 | count++
34 | mu.Unlock()
35 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
36 | }
37 | done <- true
38 | }()
39 |
40 | <-done
41 | <-done
42 | fmt.Println(count)
43 | ```
44 |
45 | `sync.Mutex`は`Lock`メソッドでロックを取り、`Unlock`メソッドでロックを解除します。
46 | すでにロックが掛かっているMutexに対して`Lock`メソッドを呼び出そうとすると、`Unlock`メソッドが呼び出されるまで処理がブロックされます。
47 |
48 | `Unlock`メソッドは`defer`で呼び出すこともありますが、`for`や再帰呼び出しなどでデッドロックを起こす可能性があるため注意が必要です。
49 |
50 | ## ゴールーチンの待ち合わせ
51 |
52 | 複数のゴールーチンの処理を待って次の処理に移りたい場合があります。
53 | 例えば、お湯を沸かすことと豆を挽くことは並列で行っても問題ありませんが、
54 | コーヒーを淹れるためには、お湯と挽いた豆が揃っている必要があります。
55 |
56 | `sync.WaitGroup`はゴールーチンの待ち合わせを行う機能を提供しています。
57 | 使い方はとてもシンプルです。
58 | `Wait`メソッドで複数のゴールーチンの処理を待ち合わせることができ、
59 | `Add`メソッドで追加した数だけ`Done`メソッドが呼ばれるまで処理がブロックされます。
60 |
61 | 例えば、次の例では`wg.Add(1)`が2回実行されているため、`wg.Done()`が2回実行されるまで
62 | `wg.Wait()`で処理をブロックします。
63 |
64 | ```go
65 | var (
66 | count int
67 | mu sync.Mutex
68 | )
69 |
70 | var wg sync.WaitGroup
71 | wg.Add(1)
72 | go func() {
73 | defer wg.Done()
74 | for i := 0; i < 10; i++ {
75 | mu.Lock()
76 | count++
77 | mu.Unlock()
78 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
79 | }
80 | done <- true
81 | }()
82 |
83 | wg.Add(1)
84 | go func() {
85 | defer wg.Done()
86 | for i := 0; i < 10; i++ {
87 | mu.Lock()
88 | count++
89 | mu.Unlock()
90 | time.Sleep(100 * time.Millisecond) // 10[ms]スリープ
91 | }
92 | }()
93 |
94 | // 2つのゴールーチンの処理が終わるまで待つ
95 | wg.Wait()
96 | fmt.Println(count)
97 | ```
98 |
99 | ## プログラムの改造
100 |
101 | チャネルを使わないようにコーヒーを淹れるプログラムを改造してみましょう。
102 |
103 | `boil`関数、`grind`関数、`brew`関数の処理結果はチャネル経由ではなく戻り値で受け取ります。
104 | 受け取った戻り値を変数に足す必要がありますが、そのまま足すと競合が起きるためロックを取って足す必要があります。
105 |
106 | `boil`関数と`grind`関数の処理は並列で実行しても問題ないため、それぞれゴールーチンで実行し、1つの`sync.WaitGroup`で待ち合わせすることにします。
107 |
108 | `brew`関数の処理はゴールーチンで呼ばれますが、別の`sync.WaitGroup`でコーヒーが全て淹れ終わるまで待つことにしましょう。
109 |
110 | ## 実行とトレースデータの表示
111 |
112 | `TODO`を埋めると次のコマンドで実行することができます。
113 |
114 | ```
115 | $ go run main.go
116 | ```
117 |
118 | `trace.out`というトレース情報を記録したファイルが出力されるため、次のコマンドで結果を表示します。
119 |
120 | ```
121 | $ go tool trace trace.out
122 | ```
123 |
124 | ブラウザが開くので、`User-defined tasks` -> `Count` -> `Task 1`の順番で開くと次のような結果が表示されれば成功です。
125 |
126 |
127 |
128 | 実行時間はSTEP 3と大きく変わりません。
129 | 同じように`boil`と`grind`が並列に処理され、その後に`grind`が処理されています。
130 | しかし、`boil`と`grind`のバーの長さが同じではなくなっています。
131 |
132 | STEP 3では`boil`からのデータの送信をすべて待った後に、`grind`からのデータを受け取っていましたが、
133 | 今回は`sync.WaitGroup`で`boil`も`grind`も関係なく待っていたので、`grind`の方が`boil`の長さに引っ張られずに終了したためです。
134 |
--------------------------------------------------------------------------------
/solution/step04/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step04
2 |
3 | go 1.14
4 |
--------------------------------------------------------------------------------
/solution/step04/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "os"
8 | "runtime/trace"
9 | "sync"
10 | "time"
11 | )
12 |
13 | type (
14 | Bean int
15 | GroundBean int
16 | Water int
17 | HotWater int
18 | Coffee int
19 | )
20 |
21 | const (
22 | GramBeans Bean = 1
23 | GramGroundBeans GroundBean = 1
24 | MilliLiterWater Water = 1
25 | MilliLiterHotWater HotWater = 1
26 | CupsCoffee Coffee = 1
27 | )
28 |
29 | func (w Water) String() string {
30 | return fmt.Sprintf("%d[ml] water", int(w))
31 | }
32 |
33 | func (hw HotWater) String() string {
34 | return fmt.Sprintf("%d[ml] hot water", int(hw))
35 | }
36 |
37 | func (b Bean) String() string {
38 | return fmt.Sprintf("%d[g] beans", int(b))
39 | }
40 |
41 | func (gb GroundBean) String() string {
42 | return fmt.Sprintf("%d[g] ground beans", int(gb))
43 | }
44 |
45 | func (cups Coffee) String() string {
46 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
47 | }
48 |
49 | // 1カップのコーヒーを淹れるのに必要な水の量
50 | func (cups Coffee) Water() Water {
51 | return Water(180*cups) / MilliLiterWater
52 | }
53 |
54 | // 1カップのコーヒーを淹れるのに必要なお湯の量
55 | func (cups Coffee) HotWater() HotWater {
56 | return HotWater(180*cups) / MilliLiterHotWater
57 | }
58 |
59 | // 1カップのコーヒーを淹れるのに必要な豆の量
60 | func (cups Coffee) Beans() Bean {
61 | return Bean(20*cups) / GramBeans
62 | }
63 |
64 | // 1カップのコーヒーを淹れるのに必要な粉の量
65 | func (cups Coffee) GroundBeans() GroundBean {
66 | return GroundBean(20*cups) / GramGroundBeans
67 | }
68 |
69 | func main() {
70 | f, err := os.Create("trace.out")
71 | if err != nil {
72 | log.Fatalln("Error:", err)
73 | }
74 | defer func() {
75 | if err := f.Close(); err != nil {
76 | log.Fatalln("Error:", err)
77 | }
78 | }()
79 |
80 | if err := trace.Start(f); err != nil {
81 | log.Fatalln("Error:", err)
82 | }
83 | defer trace.Stop()
84 |
85 | _main()
86 | }
87 |
88 | func _main() {
89 | // 作るコーヒーの数
90 | const amountCoffee = 20 * CupsCoffee
91 |
92 | ctx, task := trace.NewTask(context.Background(), "make coffe")
93 | defer task.End()
94 |
95 | // 材料
96 | water := amountCoffee.Water()
97 | beans := amountCoffee.Beans()
98 |
99 | fmt.Println(water)
100 | fmt.Println(beans)
101 |
102 | var wg sync.WaitGroup
103 |
104 | // お湯を沸かす
105 | var hotWater HotWater
106 | var hwmu sync.Mutex
107 | for water > 0 {
108 | water -= 600 * MilliLiterWater
109 | wg.Add(1)
110 | go func() {
111 | defer wg.Done()
112 | hw := boil(ctx, 600*MilliLiterWater)
113 | hwmu.Lock()
114 | defer hwmu.Unlock()
115 | hotWater += hw
116 | }()
117 | }
118 |
119 | // 豆を挽く
120 | var groundBeans GroundBean
121 | var gbmu sync.Mutex
122 | for beans > 0 {
123 | beans -= 20 * GramBeans
124 | wg.Add(1)
125 | go func() {
126 | defer wg.Done()
127 | gb := grind(ctx, 20*GramBeans)
128 | gbmu.Lock()
129 | defer gbmu.Unlock()
130 | groundBeans += gb
131 | }()
132 | }
133 |
134 | wg.Wait()
135 | fmt.Println(hotWater)
136 | fmt.Println(groundBeans)
137 |
138 | // コーヒーを淹れる
139 | var wg2 sync.WaitGroup
140 | var coffee Coffee
141 | var cfmu sync.Mutex
142 | cups := 4 * CupsCoffee
143 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
144 | hotWater -= cups.HotWater()
145 | groundBeans -= cups.GroundBeans()
146 | wg2.Add(1)
147 | go func() {
148 | defer wg2.Done()
149 | cf := brew(ctx, cups.HotWater(), cups.GroundBeans())
150 | cfmu.Lock()
151 | defer cfmu.Unlock()
152 | coffee += cf
153 | }()
154 | }
155 |
156 | wg2.Wait()
157 | fmt.Println(coffee)
158 | }
159 |
160 | // お湯を沸かす
161 | func boil(ctx context.Context, water Water) HotWater {
162 | defer trace.StartRegion(ctx, "boil").End()
163 | time.Sleep(400 * time.Millisecond)
164 | return HotWater(water)
165 | }
166 |
167 | // コーヒー豆を挽く
168 | func grind(ctx context.Context, beans Bean) GroundBean {
169 | defer trace.StartRegion(ctx, "grind").End()
170 | time.Sleep(200 * time.Millisecond)
171 | return GroundBean(beans)
172 | }
173 |
174 | // コーヒーを淹れる
175 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) Coffee {
176 | defer trace.StartRegion(ctx, "brew").End()
177 | time.Sleep(1 * time.Second)
178 | // 少ない方を優先する
179 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
180 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
181 | if cups1 < cups2 {
182 | return cups1
183 | }
184 | return cups2
185 | }
186 |
--------------------------------------------------------------------------------
/solution/step04/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gohandson/goroutine-ja/6ae2fd2ae3fa5adbae7169edb3fc5989a6cb09bc/solution/step04/trace.png
--------------------------------------------------------------------------------
/solution/step05/README.md:
--------------------------------------------------------------------------------
1 | # STEP 5: ゴールーチンとエラー処理
2 |
3 | ## エラー処理
4 |
5 | プログラムにはエラー処理はつきものです。
6 | ファイルが開けなかったり、ネットワークに接続できなかったりする可能性があります。
7 | 予想可能なエラーは適切に処理するようにプログラムを組む必要があります。
8 | コーヒーを淹れるプログラムでも、水が多すぎたり、豆が多すぎたり、お湯が少なすぎたりするかもしれません。
9 |
10 | Goでは、エラーは`error`型で表されます。
11 | エラーがあるかないかは、次のように`if`で比較することが多いでしょう。
12 |
13 | ```go
14 | err := f()
15 | if err != nil {
16 | // エラー処理
17 | }
18 |
19 | // これでもOK
20 | if err := f(); err != nil {
21 | // エラー処理
22 | }
23 | ```
24 |
25 | 受け取ったエラーはログに吐いたり、情報を付加して呼び出し元にreturnしたりします。
26 | `panic`を起こしたり、無視することは極力さけるようにしましょう。
27 |
28 | ## ゴールーチンとエラー処理
29 |
30 | 別のゴールーチンで行っている処理でエラーが発生する場合はどのようにハンドリングすればよいのでしょうか。
31 | エラーも値なのでチャネルを使って伝搬する方法が考えられます。
32 |
33 | しかし、単純な場合はチャネルでも問題ありませんが、`sync.WaitGroup`などを使っているような複雑な場合については、チャンネルを用いると逆に煩雑になってしまう恐れがあります。
34 | `golang.org/x/sync/errgroup`という準標準なパッケージを使うことで、このようなパターンをうまくハンドリングすることができます。
35 |
36 | `errgroup`パッケージには、`sync.WaitGroup`に似た`errgroup.Group`が提供されています。
37 | `sync.Wait`の`Add`メソッドや`Done`メソッドの代わりに、`Go`メソッドがあります。
38 | `Go`メソッドは引数で渡した関数をゴールーチンで実行してくれます。
39 | この関数の戻り値はerror型でゴールーチンでエラーが発生した場合には適切に処理ができるようになっています。
40 |
41 | `Wait`メソッドを実行すると、`Go`メソッドで呼び出された関数がすべて終了するまで処理がブロックされます。
42 | もし、1つでもエラーを返す関数があれば、`Wait`メソッドは戻り値で最初に発生したエラーを返します。
43 |
44 | ```go
45 | var eg errgroup.Group
46 |
47 | eg.Go(func() error {
48 | if err := f(); err != nil {
49 | return err
50 | }
51 | })
52 |
53 | eg.Go(func() error {
54 | if err := g(); err != nil {
55 | return err
56 | }
57 | })
58 |
59 | if err := eg.Wait(); err != nil {
60 | // エラー処理
61 | }
62 | ```
63 |
64 | ## プログラムの改造
65 |
66 | `boil`関数、`grind`関数、`brew`関数がエラーを返すようになっています。
67 | `sync.WaitGroup`の代わりに`errgroup.Group`を使うことでエラーハンドリングをできるようにしましょう。
68 |
69 | ## 実行
70 |
71 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。
72 |
73 | ```
74 | $ go get -u golang.org/x/sync/errgroup
75 | ```
76 |
77 | 次のコマンドで実行することができます。
78 |
79 | ```
80 | $ go run main.go
81 | ```
82 |
83 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。
84 |
--------------------------------------------------------------------------------
/solution/step05/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step05
2 |
3 | go 1.14
4 |
5 | require golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
6 |
--------------------------------------------------------------------------------
/solution/step05/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
2 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3 |
--------------------------------------------------------------------------------
/solution/step05/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "os"
9 | "runtime/trace"
10 | "sync"
11 | "time"
12 |
13 | "golang.org/x/sync/errgroup"
14 | )
15 |
16 | type (
17 | Bean int
18 | GroundBean int
19 | Water int
20 | HotWater int
21 | Coffee int
22 | )
23 |
24 | const (
25 | GramBeans Bean = 1
26 | GramGroundBeans GroundBean = 1
27 | MilliLiterWater Water = 1
28 | MilliLiterHotWater HotWater = 1
29 | CupsCoffee Coffee = 1
30 | )
31 |
32 | func (w Water) String() string {
33 | return fmt.Sprintf("%d[ml] water", int(w))
34 | }
35 |
36 | func (hw HotWater) String() string {
37 | return fmt.Sprintf("%d[ml] hot water", int(hw))
38 | }
39 |
40 | func (b Bean) String() string {
41 | return fmt.Sprintf("%d[g] beans", int(b))
42 | }
43 |
44 | func (gb GroundBean) String() string {
45 | return fmt.Sprintf("%d[g] ground beans", int(gb))
46 | }
47 |
48 | func (cups Coffee) String() string {
49 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
50 | }
51 |
52 | // 1カップのコーヒーを淹れるのに必要な水の量
53 | func (cups Coffee) Water() Water {
54 | return Water(180*cups) / MilliLiterWater
55 | }
56 |
57 | // 1カップのコーヒーを淹れるのに必要なお湯の量
58 | func (cups Coffee) HotWater() HotWater {
59 | return HotWater(180*cups) / MilliLiterHotWater
60 | }
61 |
62 | // 1カップのコーヒーを淹れるのに必要な豆の量
63 | func (cups Coffee) Beans() Bean {
64 | return Bean(20*cups) / GramBeans
65 | }
66 |
67 | // 1カップのコーヒーを淹れるのに必要な粉の量
68 | func (cups Coffee) GroundBeans() GroundBean {
69 | return GroundBean(20*cups) / GramGroundBeans
70 | }
71 |
72 | func main() {
73 | f, err := os.Create("trace.out")
74 | if err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | defer func() {
78 | if err := f.Close(); err != nil {
79 | log.Fatalln("Error:", err)
80 | }
81 | }()
82 |
83 | if err := trace.Start(f); err != nil {
84 | log.Fatalln("Error:", err)
85 | }
86 | defer trace.Stop()
87 |
88 | _main()
89 | }
90 |
91 | func _main() {
92 | // 作るコーヒーの数
93 | const amountCoffee = 20 * CupsCoffee
94 |
95 | ctx, task := trace.NewTask(context.Background(), "make coffe")
96 | defer task.End()
97 |
98 | // 材料
99 | water := amountCoffee.Water()
100 | beans := amountCoffee.Beans()
101 |
102 | fmt.Println(water)
103 | fmt.Println(beans)
104 |
105 | var eg errgroup.Group
106 |
107 | // お湯を沸かす
108 | var hotWater HotWater
109 | var hwmu sync.Mutex
110 | for water > 0 {
111 | water -= 600 * MilliLiterWater
112 | eg.Go(func() error {
113 | hw, err := boil(ctx, 600*MilliLiterWater)
114 | if err != nil {
115 | return err
116 | }
117 | hwmu.Lock()
118 | defer hwmu.Unlock()
119 | hotWater += hw
120 | return nil
121 | })
122 | }
123 |
124 | // 豆を挽く
125 | var groundBeans GroundBean
126 | var gbmu sync.Mutex
127 | for beans > 0 {
128 | beans -= 20 * GramBeans
129 | eg.Go(func() error {
130 | gb, err := grind(ctx, 20*GramBeans)
131 | if err != nil {
132 | return err
133 | }
134 | gbmu.Lock()
135 | defer gbmu.Unlock()
136 | groundBeans += gb
137 | return nil
138 | })
139 | }
140 |
141 | if err := eg.Wait(); err != nil {
142 | fmt.Fprintln(os.Stderr, "Error:", err)
143 | return
144 | }
145 | fmt.Println(hotWater)
146 | fmt.Println(groundBeans)
147 |
148 | // コーヒーを淹れる
149 | var eg2 errgroup.Group
150 | var coffee Coffee
151 | var cfmu sync.Mutex
152 | cups := 4 * CupsCoffee
153 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
154 | hotWater -= cups.HotWater()
155 | groundBeans -= cups.GroundBeans()
156 | eg2.Go(func() error {
157 | cf, err := brew(ctx, cups.HotWater(), cups.GroundBeans())
158 | if err != nil {
159 | return err
160 | }
161 | cfmu.Lock()
162 | defer cfmu.Unlock()
163 | coffee += cf
164 | return nil
165 | })
166 | }
167 |
168 | if err := eg2.Wait(); err != nil {
169 | fmt.Fprintln(os.Stderr, "Error:", err)
170 | return
171 | }
172 | fmt.Println(coffee)
173 | }
174 |
175 | // お湯を沸かす
176 | func boil(ctx context.Context, water Water) (HotWater, error) {
177 | defer trace.StartRegion(ctx, "boil").End()
178 | if water > 600*MilliLiterWater {
179 | return 0, errors.New("1度に沸かすことのできるお湯は600[ml]までです")
180 | }
181 | time.Sleep(400 * time.Millisecond)
182 | return HotWater(water), nil
183 | }
184 |
185 | // コーヒー豆を挽く
186 | func grind(ctx context.Context, beans Bean) (GroundBean, error) {
187 | defer trace.StartRegion(ctx, "grind").End()
188 | if beans > 20*GramBeans {
189 | return 0, errors.New("1度に挽くことのできる豆は20[g]までです")
190 | }
191 | time.Sleep(200 * time.Millisecond)
192 | return GroundBean(beans), nil
193 | }
194 |
195 | // コーヒーを淹れる
196 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) (Coffee, error) {
197 | defer trace.StartRegion(ctx, "brew").End()
198 |
199 | if hotWater < (1 * CupsCoffee).HotWater() {
200 | return 0, errors.New("お湯が足りません")
201 | }
202 |
203 | if groundBeans < (1 * CupsCoffee).GroundBeans() {
204 | return 0, errors.New("粉が足りません")
205 | }
206 |
207 | time.Sleep(1 * time.Second)
208 | // 少ない方を優先する
209 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
210 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
211 | if cups1 < cups2 {
212 | return cups1, nil
213 | }
214 | return cups2, nil
215 | }
216 |
--------------------------------------------------------------------------------
/solution/step06/README.md:
--------------------------------------------------------------------------------
1 | # STEP 6: コンテキストとキャンセル処理
2 |
3 | ## キャンセル処理
4 |
5 | とあるゴールーチンでエラーが発生した場合に、
6 | 他のゴールーチンの処理をキャンセルしたい場合があります。
7 |
8 | Goでは、ゴールーチンのキャンセル処理のために`context.Context`を用います。
9 | `context.WithCancel`関数でラップしたコンテキストは第2戻り値返されたキャンセル用の関数が呼び出されるか、親のコンテキストがキャンセルされるとキャンセルされます。
10 | キャンセルされたことを知るには、`context.Context`の`Done`メソッドから返ってくるチャネルを用います。
11 |
12 | 次の例では、2つのゴールーチンを立ち上げ、キャンセルを`Done`メソッドのチャネルで伝えています。
13 | `select`は複数のチャンネルへの送受信を待機することのできる構文で、どのケースのチャネルも反応しない場合は、`default`が実行されます。
14 |
15 | ```go
16 | func main() {
17 | root := context.Background()
18 | ctx1, cancel := context.WithCancel(root)
19 | ctx2, _ := context.WithCancel(ctx1)
20 |
21 | var wg sync.WaitGroup
22 | wg.Add(2) // 2つのゴールーチンが終わるの待つため
23 |
24 | go func() {
25 | defer wg.Done()
26 | for {
27 | select {
28 | case <-ctx2.Done():
29 | fmt.Println("cancel goroutine1")
30 | return
31 | default:
32 | fmt.Println("waint goroutine1")
33 | time.Sleep(500 * time.Millisecond)
34 | }
35 | }
36 | }()
37 |
38 | go func() {
39 | defer wg.Done()
40 | for {
41 | select {
42 | case <-ctx2.Done():
43 | fmt.Println("cancel goroutine2")
44 | return
45 | default:
46 | fmt.Println("waint goroutine2")
47 | time.Sleep(500 * time.Millisecond)
48 | }
49 | }
50 | }()
51 |
52 | time.Sleep(2 * time.Second)
53 | cancel()
54 | wg.Wait()
55 | }
56 | ```
57 |
58 | ## errgroup.Groupを使ったキャンセル処理
59 |
60 | `errgroup.Group`は`errgroup.WithContext`を用いることで、エラーが起きた際に処理をキャンセルすることができます。
61 |
62 | ```go
63 | func main() {
64 | root := context.Background()
65 | eg, ctx := errgroup.WithContext(root)
66 |
67 | eg.Go(func() error {
68 | for {
69 | select {
70 | case <-ctx.Done():
71 | fmt.Println("cancel goroutine1")
72 | return nil
73 | default:
74 | fmt.Println("waint goroutine1")
75 | time.Sleep(500 * time.Millisecond)
76 | }
77 | }
78 | })
79 |
80 | eg.Go(func() error {
81 | time.Sleep(2 * time.Second)
82 | return errors.New("error")
83 | })
84 |
85 | if err := eg.Wait(); err != nil {
86 | log.Fatal(err)
87 | }
88 | }
89 | ```
90 |
91 | ## プログラムの改造
92 |
93 | `errgroup.WithContext`を用いてエラーが発生した場合のキャンセル処理をハンドリングしましょう。
94 |
95 | ## 実行
96 |
97 | `errgroup`パッケージは外部パッケージであるため、`go get`コマンドでインストールする必要があります。
98 |
99 | ```
100 | $ go get -u golang.org/x/sync/errgroup
101 | ```
102 |
103 | 次のコマンドで実行することができます。
104 |
105 | ```
106 | $ go run main.go
107 | ```
108 |
109 | `boil`関数に渡す水の量を2倍にしたりしてエラーが発生するようにしてみましょう。
110 |
--------------------------------------------------------------------------------
/solution/step06/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gohandson/goroutine-ja/solution/step06
2 |
3 | go 1.14
4 |
5 | require golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
6 |
--------------------------------------------------------------------------------
/solution/step06/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
2 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
3 |
--------------------------------------------------------------------------------
/solution/step06/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "os"
9 | "runtime/trace"
10 | "sync"
11 | "time"
12 |
13 | "golang.org/x/sync/errgroup"
14 | )
15 |
16 | type (
17 | Bean int
18 | GroundBean int
19 | Water int
20 | HotWater int
21 | Coffee int
22 | )
23 |
24 | const (
25 | GramBeans Bean = 1
26 | GramGroundBeans GroundBean = 1
27 | MilliLiterWater Water = 1
28 | MilliLiterHotWater HotWater = 1
29 | CupsCoffee Coffee = 1
30 | )
31 |
32 | func (w Water) String() string {
33 | return fmt.Sprintf("%d[ml] water", int(w))
34 | }
35 |
36 | func (hw HotWater) String() string {
37 | return fmt.Sprintf("%d[ml] hot water", int(hw))
38 | }
39 |
40 | func (b Bean) String() string {
41 | return fmt.Sprintf("%d[g] beans", int(b))
42 | }
43 |
44 | func (gb GroundBean) String() string {
45 | return fmt.Sprintf("%d[g] ground beans", int(gb))
46 | }
47 |
48 | func (cups Coffee) String() string {
49 | return fmt.Sprintf("%d cup(s) coffee", int(cups))
50 | }
51 |
52 | // 1カップのコーヒーを淹れるのに必要な水の量
53 | func (cups Coffee) Water() Water {
54 | return Water(180*cups) / MilliLiterWater
55 | }
56 |
57 | // 1カップのコーヒーを淹れるのに必要なお湯の量
58 | func (cups Coffee) HotWater() HotWater {
59 | return HotWater(180*cups) / MilliLiterHotWater
60 | }
61 |
62 | // 1カップのコーヒーを淹れるのに必要な豆の量
63 | func (cups Coffee) Beans() Bean {
64 | return Bean(20*cups) / GramBeans
65 | }
66 |
67 | // 1カップのコーヒーを淹れるのに必要な粉の量
68 | func (cups Coffee) GroundBeans() GroundBean {
69 | return GroundBean(20*cups) / GramGroundBeans
70 | }
71 |
72 | func main() {
73 | f, err := os.Create("trace.out")
74 | if err != nil {
75 | log.Fatalln("Error:", err)
76 | }
77 | defer func() {
78 | if err := f.Close(); err != nil {
79 | log.Fatalln("Error:", err)
80 | }
81 | }()
82 |
83 | if err := trace.Start(f); err != nil {
84 | log.Fatalln("Error:", err)
85 | }
86 | defer trace.Stop()
87 |
88 | _main()
89 | }
90 |
91 | func _main() {
92 | // 作るコーヒーの数
93 | const amountCoffee = 20 * CupsCoffee
94 |
95 | taskCtx, task := trace.NewTask(context.Background(), "make coffe")
96 | defer task.End()
97 |
98 | // 材料
99 | water := amountCoffee.Water()
100 | beans := amountCoffee.Beans()
101 |
102 | fmt.Println(water)
103 | fmt.Println(beans)
104 |
105 | eg, ctx := errgroup.WithContext(taskCtx)
106 |
107 | // お湯を沸かす
108 | var hotWater HotWater
109 | var hwmu sync.Mutex
110 | for water > 0 {
111 | water -= 600 * MilliLiterWater
112 | eg.Go(func() error {
113 | select {
114 | case <-ctx.Done():
115 | trace.Log(ctx, "boil error", ctx.Err().Error())
116 | return ctx.Err()
117 | default:
118 | }
119 | hw, err := boil(ctx, 600*MilliLiterWater)
120 | if err != nil {
121 | return err
122 | }
123 | hwmu.Lock()
124 | defer hwmu.Unlock()
125 | hotWater += hw
126 | return nil
127 | })
128 | }
129 |
130 | // 豆を挽く
131 | var groundBeans GroundBean
132 | var gbmu sync.Mutex
133 | for beans > 0 {
134 | beans -= 20 * GramBeans
135 | eg.Go(func() error {
136 | select {
137 | case <-ctx.Done():
138 | trace.Log(ctx, "grind error", ctx.Err().Error())
139 | return ctx.Err()
140 | default:
141 | }
142 |
143 | gb, err := grind(ctx, 20*GramBeans)
144 | if err != nil {
145 | return err
146 | }
147 | gbmu.Lock()
148 | defer gbmu.Unlock()
149 | groundBeans += gb
150 | return nil
151 | })
152 | }
153 |
154 | if err := eg.Wait(); err != nil {
155 | fmt.Fprintln(os.Stderr, "Error:", err)
156 | return
157 | }
158 | fmt.Println(hotWater)
159 | fmt.Println(groundBeans)
160 |
161 | // コーヒーを淹れる
162 | eg, ctx = errgroup.WithContext(taskCtx)
163 | var coffee Coffee
164 | var cfmu sync.Mutex
165 | cups := 4 * CupsCoffee
166 | for hotWater >= cups.HotWater() && groundBeans >= cups.GroundBeans() {
167 | hotWater -= cups.HotWater()
168 | groundBeans -= cups.GroundBeans()
169 | eg.Go(func() error {
170 | select {
171 | case <-ctx.Done():
172 | trace.Log(ctx, "brew error", ctx.Err().Error())
173 | return ctx.Err()
174 | default:
175 | }
176 |
177 | cf, err := brew(ctx, cups.HotWater(), cups.GroundBeans())
178 | if err != nil {
179 | return err
180 | }
181 | cfmu.Lock()
182 | defer cfmu.Unlock()
183 | coffee += cf
184 | return nil
185 | })
186 | }
187 |
188 | if err := eg.Wait(); err != nil {
189 | fmt.Fprintln(os.Stderr, "Error:", err)
190 | return
191 | }
192 | fmt.Println(coffee)
193 | }
194 |
195 | // お湯を沸かす
196 | func boil(ctx context.Context, water Water) (HotWater, error) {
197 | defer trace.StartRegion(ctx, "boil").End()
198 | if water > 600*MilliLiterWater {
199 | return 0, errors.New("1度に沸かすことのできるお湯は600[ml]までです")
200 | }
201 | time.Sleep(400 * time.Millisecond)
202 | return HotWater(water), nil
203 | }
204 |
205 | // コーヒー豆を挽く
206 | func grind(ctx context.Context, beans Bean) (GroundBean, error) {
207 | defer trace.StartRegion(ctx, "grind").End()
208 | if beans > 20*GramBeans {
209 | return 0, errors.New("1度に挽くことのできる豆は20[g]までです")
210 | }
211 | time.Sleep(200 * time.Millisecond)
212 | return GroundBean(beans), nil
213 | }
214 |
215 | // コーヒーを淹れる
216 | func brew(ctx context.Context, hotWater HotWater, groundBeans GroundBean) (Coffee, error) {
217 | defer trace.StartRegion(ctx, "brew").End()
218 |
219 | if hotWater < (1 * CupsCoffee).HotWater() {
220 | return 0, errors.New("お湯が足りません")
221 | }
222 |
223 | if groundBeans < (1 * CupsCoffee).GroundBeans() {
224 | return 0, errors.New("粉が足りません")
225 | }
226 |
227 | time.Sleep(1 * time.Second)
228 | // 少ない方を優先する
229 | cups1 := Coffee(hotWater / (1 * CupsCoffee).HotWater())
230 | cups2 := Coffee(groundBeans / (1 * CupsCoffee).GroundBeans())
231 | if cups1 < cups2 {
232 | return cups1, nil
233 | }
234 | return cups2, nil
235 | }
236 |
--------------------------------------------------------------------------------