Skip to content

Commit 8d989fc

Browse files
committed
Move from tenntenn/gohandson
0 parents  commit 8d989fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3647
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.out

README.md

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# 分かるゴールーチンとチャネル
2+
3+
## はじめに
4+
5+
このハンズオンでは、コーヒーを淹れるという作業を例にゴールーチンを扱います。
6+
基本的な文法などは、このハンズオンでは扱いません。
7+
そのため、ハンズオンを始める前に、[A Tour of Go](https://go-tour-jp.appspot.com)を終わらせると良いでしょう。
8+
また、文法や周辺ツール、`GOPATH`などの詳しい説明は[公式ドキュメント](https://golang.org/doc/)の「Learning Go」の項目を読んでください。
9+
特に他のオブジェクト指向言語などを学習されている方は、「FAQ」に目を通すとよいでしょう。
10+
11+
英語が辛い方は、有志によって[翻訳されたドキュメント](http://golang-jp.org/doc/)の「Goを学ぶ」を読んで下さい。
12+
すべてが翻訳されているわけではありませんが、役に立つでしょう。
13+
また、[はじめてのGo](http://gihyo.jp/dev/feature/01/go_4beginners)も日本語で書かれていて分かりやすいのでぜひ読んで下さい。
14+
15+
標準パッケージについては、[パッケージドキュメント](https://golang.org/pkg/)を見ると、使い方が説明されています。
16+
17+
## 学べることと学べないこと
18+
19+
このハンズオンを行うと以下のことが学べます。
20+
21+
* ゴールーチンとチャネルの基本
22+
* トレースの方法
23+
* `sync`パッケージの使い方
24+
* ゴールーチンとエラー処理
25+
* コンテキストとキャンセル処理
26+
27+
一方、学べないことは以下のとおりです。
28+
29+
* Goの開発環境のインストール方法
30+
* IDEやエディタの設定
31+
* 基本的な文法
32+
* コマンドラインツールの作り方
33+
* net/httpパッケージ
34+
35+
## ハンズオンのやりかた
36+
37+
`skeleton`ディレクトリ以下に問題があり、6つのステップに分けられています。
38+
STEP 1からSTEP 6までステップごとに進めていくことで、並行処理に関する知識が学べます。
39+
40+
各ステップに、READMEが用意されていますので、まずは`README`を読みます。
41+
`README`には、そのステップを理解するための解説が書かれています。
42+
43+
`README`を読んだら、ソースコードを開き`TODO`コメントが書かれている箇所をコメントに従って修正して行きます。
44+
`TODO`コメントをすべて修正し終わったら、`README`に書かれた実行例に従ってプログラムをコンパイルして実行します。
45+
46+
途中でわからなくなった場合は、`solution`ディレクトリ以下に解答例を用意していますので、そちらをご覧ください。
47+
48+
`Mac``Windows`で動作確認をしていますが、解説は`Mac`の動作結果をもとに解説しています。
49+
`Windows`の方は、パスの区切り文字等を適宜読み替えてください。
50+
51+
## 目次
52+
53+
* STEP 1: [ゴールーチンを使わずに処理する](./skeleton/step01)[解答例](./solution/step01)
54+
* STEP 2: [ボトルネックを探す](./skeleton/step02)[解答例](./solution/step02)
55+
* STEP 3: [ゴールーチンとチャネル](./skeleton/step03)[解答例](./solution/step03)
56+
* STEP 4: [syncパッケージを使う](./skeleton/step04)[解答例](./solution/step04)
57+
* STEP 5: [ゴールーチンとエラー処理](./skeleton/step05)[解答例](./solution/step05)
58+
* STEP 6: [コンテキストとキャンセル処理](./skeleton/step06)[解答例](./solution/step06)
59+
60+
## ハンズオンの開催や資料の扱いについて
61+
この資料を元にハンズオンを開催するために@tenntennの許可などはいりません。
62+
好きに開催してください。
63+
@tenntennの行ける範囲であれば、開催するから解説して欲しいという依頼もウェルカムです。
64+
65+
なお、forkして変更してもらっても構いませんが、できればPRをいただけると嬉しいです。
66+
資料に間違えを発見した方もissueやPRを頂ければ対応します。
67+
68+
## ライセンス
69+
70+
<a href="https://creativecommons.org/licenses/by-nc/4.0/legalcode.ja">
71+
<img width="200" src="by-nc.eu.png">
72+
</a>

by-nc.eu.png

9.1 KB
Loading

skeleton/step01/README.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
```

skeleton/step01/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/gohandson/goroutine-ja/solution/step01
2+
3+
go 1.14

skeleton/step01/main.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
}

0 commit comments

Comments
 (0)