プログラミング言語Go
1
p.4 スライスのインデックスの指定はhalf-open
ロジックがかんたん
p.5 型の指定は valname stringみたいな感じか
p.24 race condition => 9章へ
p.26 switch-caseあります
break不要
C言語のようにしたければ、fallthrough文を使う
breakやcontinueはラベルに対して飛ばせるので、複数ループ抜けるとかも書ける。
p.26 ポインタがある
ポインタを明示的に扱えるが、ポインタへの演算はない。
多分、pointer++ とかはできない。 mactkg.icon
不正アクセスを引き起こす原因になるから
2
p.30 名前
スコープが広ければ広いほど、名前は長くて意味を持つものとなっているべき
キャメルケースを好む
htmlEscape HTMLEscape escapeHTMLはOKだけど、escapeHtmlはしない。
宣言
パッケージレベルのエンティティの名前は、そのパッケージのすべてのソースファイルで見える。
変数
Goには未初期化の変数というものはありません。これはコードを単純にしてくれて、たいていは余分な作業無しで、境界条件での理にかなった振る舞いを保証します。
多くの場合、Goプログラマは複雑な型であってもゼロ値が有効な値になるように設計する努力をしますので、変数は有効な状態から始まります。
省略変数宣言 :=
var foo = 42 と foo := 42 は同じ
省略変数宣言は左辺のすべてを宣言するとは限らない。例えばこう
code:ex.go
var foo = 42
foo, err := getIntegerFromInternet() // errだけ宣言する
ポインタ
Goにもポインタはある
ポインタへの演算は出来ない
invalid operation: p++ (non-numeric type *int)
code:pointer.go
x := 1
p := &x // pはintへのポインタ(*int)で、xのアドレスを持っている
fmt.Println(*p) // 1
*p = 2 // xに間接的にアクセスして、書き換える
fmt.Println(x) // もちろん2
変数の生存期間 - lifetime
変数が到達可能か否かをGCはみている
コンパイラはローカル変数をヒープに置くか、スタックに置くかを決める
変数xは関数fからエスケープしている例
code:escape.go
var global *int
func f() {
x := 1
global = &x // f
}
関数fの領域を抜けて、その外へ逃げている
この場合、ヒープに置かれることになる。
ヒープに置かれると新たにmallocが必要になるのでパフォーマンス上の問題点がある
また、ヒープに置かれるとGCする必要があるので関しの必要が生じる
普段はそこまで気にする必要はないがパフォーマンスを気にしたい場合は気に留めておく必要がある
グローバル変数に短命なオブジェクトへの不要なポイントを維持しておくと、ガベージコレクタによるその短命なオブジェクトの回収を阻害することになります
型宣言
type name underlying-type
規定型(underlying-type)を持つ名前付き型(named type)を定義する
同じ型でも違う表現をしていることがあるので、それらを混在させたくない
例: type Celsius float64とtype Fahrenheit float64
-.icon
go doc -src strings.Join
ソースコード読めて便利
-.icon
3
Goの型
基本型、合成型、参照型、インタフェース型
3では基本型について
rune = int32, UTF-8を表す。
8byte * 4 = 32byte
utf8mb4
60
明らかに負にならないものに対しても符号付きをつかう
そうでないとforでi--しながら回すときに悲惨なことになる
オーバーフローして 2 => 1 => 0 => INT_MAX になってしまう
61
6段落目
異なる型をいちいちキャストするのが面倒だけどどうにかならないのか mactkg.icon
71
ifの数字の例が不明
-.icon
4 composit type
96
スライスを拡張できるのおもしろい
code: slices.go
97
配列は比較できる。スライスはできない(書かないといけない)
比較できない理由: 98
1. スライスは自分自身を含める事ができる
2. 参照なので、計算してるうちに値が変わってしまうかもしれない
なので、参照同一性のみが比較される
shallowな同値性
唯一、nilとだけ比較できる。
99
cap copy
cap知らなかった
106
存在しないキーには初期値が入っているから安全ってのは最高 mactkg.icon
マップの要素のアドレスは得られない
マップが大きくなったらリハッシングされて移動するかもしれないから
メモリを意識するのがたのしい mactkg.icon
107
マップそのものを初期化せずに値を入れようとすると怒られる
マップそのものはあくまで参照なのかな?
109
キーになにか複雑なものを入れたいときはIDをつくってやればいい
114
構造体は自分自身を含めない、ただポインタを含むことはできる
119
無名フィールド便利!!!
composition!!
-.icon
5 function
code:function_example.go
func name(parameter-list) (result-list) {
// body
}
146
エラーの処理戦略いいのでまとめたい
-.icon
8 goroutine
chan
non buffer
buffer
練習問題
8.1
clockwall書くのめんどくさそう
clock2を修正してポート番号を受け付けるようにする。
Dockerでくるんで時刻をずらすとよさそう
clockwall 世界時計を作る。
runtime.Goexit()でメインのゴルーチンを終了できるよ。
8.2
がんばろうな
コネクションは2つ張ったら良いと思う
コネクションのやり取りにチャネルを使うと便利か?
x before y or not
XがYより前、あるいはXとYの順番はわからない
syncするとかで、変数の型に意味がないときはstruct{}を使う習慣がある
cap(ch), len(ch), close(ch)
練習問題
8.3
netcat3は*net.TCPConnを持っているけど、CloseReadとCloseWriteがあってそれぞれ独立に閉じれる。
netcat3をいじって標準入力が閉じた後でもプログラムがreverb1からエコーの表示を続けられるようにする。
reverb2対応が難しいらしい => 8.4へ
そのようにしたらよい
for x, ok := range ch {} がかける。
sync.waitGroup() 活用すると便利
wg.Add(1), wg.Done(), wg.Wait()
練習問題
8.4
動いてるecho goroutineを数えるために接続ごとにwaitgroupをつかうようにreverb2をいじる
ゼロになったら8.3みたいな感じでTCPの書き込み側を閉じる。
8.3のnetcat3クライアントは標準入力が閉じられた後でも複数の平行な叫びの最後のエコーを待つか?
8.5
3.3のマンデルブロ集合のやつとか3.1の3D面のやつを選んで、メインループを並列化してみよう
どれくらい早くなるか。また、最適なゴルーチンの数はどれくらいか。
バッファをいじれるようにして、ベンチとる
これすでにやってるので、ベンチマークだけかこう
カウンティングセマフォ
スロットみたいな考え方。20枠バッファ付きのチャンネルを作って: buffer := make(chan struct{}, 20)
buffer <- struct{}{} で枠取得(実際には、bufferに突っ込む: push)
<-buffer で開放(bufferから、一つとる: pop)
これを使えば、20枠のワーカーとかが作れる。
ISUCONみたい。バッファの枠を調整したらレベル挙げるベンチマーカー作れるよね mactkg.icon 練習問題
8.6
crawl3に深さ制限をかけよう
-depth=3 ってしたら3つまで
seenを[string]intにしたらいいんじゃないかな。こんなかんじ
code:ex86.go
work <- worklist
if count > 2 {
// do it
foundLinks := crawl(work.target)
} else {
return
}
んーなんかもうちょいきれいにかけそう
8.7
ローカルにディレクトリを書き出そう
元のドメインのぺーじだけ取り出そう
URLはミラーされたページを見るように書き換えよう
これはなんか前やったやつをぱくってきたらいいよ
select-case
channelを待てるswitch
channelのleak
channelのゼロ値はnilで、nilは永遠に待つ == select-caseで選ばれない
channelをnilにしたりしなかったりで、動作をトグルできるぞ!
non-blocking
8.8
8.3のエコーサーバにタイムアウトを追加して10秒何もしなかったら切ろう
やるだけ!
ゴルーチンってやっぱりコルーチンから来てるのかな?
でも処理途中で止めたりはできなくないかしら。
8.9
lsして出てきたディレクトリにduするって感じかな?
キャンセル
broadcast したければ、channelをCloseする技法を使う
p.291 3段落目
プロファイリングの手法について知りたい mactkg.icon
テスト用テクニック
キャンセルのイベントでmainから戻るかわりに、panic() させる
ダンプが出るから、メインのゴルーチン以外が残ってたら後処理できてないということ
練習問題
8.10
やってみよう、ちょっとやってみないとわからんね
8.11
select-caseを活用 & cancelパターンでリクエストをキャンセル(8.10の流用)
チャット
-.icon
9
プログラムの中のすべての具象型を並行的に実行しなくても、プログラムを並行的に安全にできる。
1. 単一のゴルーチンに変数を閉じ込める
2. 相互排他の高度な不変式を維持する
Happens Before
データ競合
sync.Mutex
deferを使えば、Mutexを安全に扱える
平行なプログラムでは常にそうであるように、明瞭性を優先し早まった最適化には抵抗してください。
二回目のmutex.Lock()が失敗することを「再入性がない」という
mutexなしであれこれできる非公開のメソッドを作る。mutexでlock取ってることが前提のメソッドを作って、それを扱う方法。(p.307)
sync.RWMutex
読み出すだけなら別に平行に実行しても問題ない
ReadとWriteでMutexを分ける
mutex.RLock()
なぜMutexが必要か
書き込み中に読まれたくない
マルチコア
マルチコアは当たり前
効率化のために、それぞれのプロセッサがローカルキャッシュを持っていることも多い。
基本バッファリングして、必要なときだけ主メモリに書き込むことも少なくない。
Goroutineの順番とは異なる順序で実行されることだってある。
次の文を実行したときに、前の変数の変更が反映されているとは限らない。
つまり、goroutineは「逐次的に一貫」sequentially consistent 並行処理のベストプラクティス
単純な場所では、変数を単一のgoroutineに閉じ込めるべし
そうでないなら、相互排他を活用すべし
9.5
sync.Once
初期化を遅延するのはよいこと
lazy!
sync.RWMutexを使って、初期化されているかをチェックした後に、されてなければ初期化、とかかけるけど、「クリティカルセクション」が複数存在して複雑。
sync.Onceなら一回実行されることを保証できる
競合状態があるかも、というときは -raceを使え
concurrent non-blocking cache
memoizing
重複抑制
cacheが無くて取りに行っている間に、他の人もcacheがないことに気づき、重複してデータを取りに行ってしまう問題を抑制するための手法
Promiseぽい
code:entry.go
type entry struct {
res result
ready chan struct{}
}
// usage
var mu = sync.Mutex{}
func Get(key string) result {
mu.Lock()
if !ok {
v = &entry{ready: make(chan struct{})} // 同期用のchanだけつくる
mu.Unlock() // ここでunlockできるのが結構意外だけど、たしかに。同期はchanでするので
v.res = fetch(key)
close(ready) // デキター
} else {
mu.Unlock()
<- v.ready // あったら待つ。もし取得済みならCloseされてるんで素通り
}
return v.res
}
-.icon
10 package and go tools
パッケージ
カプセル化
変更を気にせず使える
モジュール性
再利用しやすい
Goのビルドが早い理由
すべてのインポートはソースファイルの戦闘で明示的に列挙することが決まっている => 解決が早い
パッケージの依存性はDAGを構成していて循環がないので、基本並列にコンパイルできる コンパイル済みのGoパッケージは依存先の公開情報も記録している。
オブジェクトファイルを読み込むものの、それらのオブジェクトファイル以外は読み込まなくていい
ここ、何を具体的に記録しているのか知りたいかも mactkg.icon
パッケージ宣言 package fooについて
コマンドを定義しているパッケージは、とにかくpackage main
_test.goでファイル名が終わっているなら、パッケージ名に_testをsuffixでつけてもOK
こういうディレクトリは2つのパッケージを定義できる。
やること
go testでbuild対象であることを示す
DAGを作るときに循環を避ける
gopkg.in/yaml.v2みたいにバージョン番号をつけることができるが、バージョン番号は除外され、yamlになる
import
改名インポートできる。
import ( mrand "math/rand" )
13
unsafe.PointerはGCされないけど、uintptrはただのアドレスなので、古い可能性がある
issue 7192