├── .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 | --------------------------------------------------------------------------------