さて、引き続きやっていきます。A Tour of Go の Methods and interfacesからやっていきます。
メソッド
普通のオブジェクト指向言語にあるようなメソッドみたいに構造体やクラス内にメソッドを定義するというよりはレシーバーという特別な第一引数の値を受け取るような感じで関数を定義するとメソッドになるみたいです。 C# でいう拡張メソッドのイメージに近いなと感じました。あとは、C++ 言語の内部実装でメソッドの第一引数はクラスへのポインターだとかいうのを昔見た記憶があるので、それに近いなぁと思いました。
定義方法は func キーワードと関数名の間に括弧でくくってレシーバーを指定します。
package main import"fmt"type Person struct { Name string } func (p Person) SelfIntroduction() { fmt.Printf("%v です!", p.Name) } func main() { p := Person{"Tanaka"} p.SelfIntroduction() // Tanaka です! }
特に問題なし。
レシーバー関数は同じパッケージ内の型になら定義できる。 別パッケージの型に対して関数生やしてやるみたいな C# の拡張メソッドみたいなことは出来ないみたいです。因みに、構造体じゃなくて以下のようなものも OK。
package main import"fmt"type Person stringfunc (p Person) SelfIntroduction() { fmt.Printf("%v です!", p) } func main() { p := Person("Tanaka") p.SelfIntroduction() // Tanaka です! }
ポインターに対してレシーバーを定義できる。この場合はレシーバーの中身を書き換えることが出来る。
package main import"fmt"type Person struct { Name string } func (p Person) SelfIntroduction() { fmt.Printf("%v です!", p.Name) } func (p *Person) UpdateName(name string) { p.Name = name } func main() { p := Person{"Tanaka"} p.SelfIntroduction() // Tanaka です! p.UpdateName("Kimura") p.SelfIntroduction() // Kimura です! }
ポインターレシーバーの方がコピーではなくポインター渡しなので大きな構造体の場合などで効率がいいみたい。
interface
Go のインターフェースは他の言語と違って実装されない。 インターフェースに定義されてるものを持ってる型は、そのインターフェースに代入出来るみたい。
package main import"fmt"type Greeter interface { Greet() } type Person struct { Name string } func (p Person) Greet() { fmt.Printf("%v です!\n", p.Name) } func SelfIntrocuction(greeter Greeter) { fmt.Println("自己紹介をしてもらいます!") greeter.Greet() fmt.Println("ありがとうございました!") } func main() { p := Person{"Tanaka"} SelfIntrocuction(p) }
Greeter インターフェースと Person 構造体の間には何の継承関係も実装したとかいうのも書いてないですが Greeter を引数に受け取る SelfIntroduction 関数に渡せます。
さらに面白いのがインターフェースが nil になるケース。純粋に nil の場合は…
package main import"fmt"type Greeter interface { Greet() } func main() { var g Greeter // nil g.Greet() // panic }
実行するとこうなる。
panic: runtime error: invalid memory address or nil pointer dereference
思った通り。でも以下のようにすると…
package main import"fmt"type Greeter interface { Greet() } type Person struct { Name string } func (p *Person) Greet() { fmt.Println("Person#Greet called") } func main() { var p *Person // nilvar g Greeter = p // nil g.Greet() // ok!! }
まじかぁ…。これはインターフェースが実際には値を型を持っているからみたいです。最初にエラーになったのは本当に nil でエラーにならなかったほうは nil に見せかけて実は値が nil で型に *Person という情報を持ってるものなので、メソッド呼び出しても panic にはならない。
Person の Greet 関数で p にアクセスするとそこで panic になる。
package main import"fmt"type Greeter interface { Greet() } type Person struct { Name string } func (p *Person) Greet() { fmt.Println(p.Name) // panic } func main() { var p *Person var g Greeter = p g.Greet() }
一般的にはレシーバーに nil が渡って来ても動くことが望ましい場合は動くように作るみたいですね。
package main import"fmt"type Greeter interface { Greet() } type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } func main() { var p *Person var g Greeter = p g.Greet() }
もう 1 つ面白い挙動としてレシーバーがポインターの場合とポインターじゃないものが混在してるケース。
これはコンパイルエラー
package main import"fmt"type Greeter interface { Greet() Detail() } type Person struct { Name string } // Greet はレシーバーがポインターfunc (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } // Detail はレシーバーがポインターじゃないfunc (p Person) Detail() { fmt.Println(p.Name, " detail") } func main() { var g Greeter = Person{"Name"} g.Greet() g.Detail() }
main の中で Person{"Name"} を &Person{"Name"} にすると OK。
package main import"fmt"type Greeter interface { Greet() Detail() } type Person struct { Name string } // Greet はレシーバーがポインターfunc (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } // Detail はレシーバーがポインターじゃないfunc (p Person) Detail() { fmt.Println(p.Name, " detail") } func main() { var g Greeter = &Person{"Name"} g.Greet() g.Detail() }
Greet を実装してるのはポインターのほうであってポインターじゃない奴はインターフェース実装してないじゃん?ってことでエラーになるみたいです。ポインターを渡しておけば、どっちでもいいみたい。
そして、空のインターフェースというのがある。これは Java や C# における object 型みたいに使えるみたい。何のメソッドも持たないインターフェースには何でも代入できるといった代物。interface{}
と書く。
package main import"fmt"type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } func main() { var i interface{} = Person{"Tanaka"} fmt.Printf("%v, %T\n", i, i) }
実行するとこんな感じ
{Tanaka}, main.Person
他の言語でいうダウンキャストは、タイプアサーションっていうみたい。
.(型名)
でタイプアサーションが出来て、戻り値は 2 つでダウンキャストした結果と、成否が返ってくる。2つ目の戻り値を受け取らないでタイプアサーションに失敗すると panic になる。
package main import"fmt"type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } func main() { var i interface{} = Person{"Tanaka"} if p, ok := i.(Person); ok { p.Greet() } else { fmt.Println("failed") } }
これは OK。実行すると Tanaka と表示されます。i に別の型が入っていると failed と表示されます。こんな感じ。
package main import"fmt"type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } type Dummy struct{} func (d *Dummy) Greet() {} func main() { var i interface{} = Dummy{} if p, ok := i.(Person); ok { p.Greet() } else { fmt.Println("failed") } }
ok を受け取らないようにすると panic になる。こんな感じ
package main import"fmt"type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } type Dummy struct{} func (d *Dummy) Greet() {} func main() { var i interface{} = Dummy{} p := i.(Person) // panic p.Greet() }
最近の言語は結構な割合で持ってる型スイッチを Go も持ってる。.(type)
っていうのを switch に渡す。
package main import"fmt"type Person struct { Name string } func (p *Person) Greet() { if p == nil { fmt.Println("nil!!") return } fmt.Println(p.Name) } type Dummy struct{} func (d *Dummy) Greet() {} func NotTypeSwitchFunction(i interface{}) { if t, ok := i.(Person); ok { t.Greet() return } if _, ok := i.(Dummy); ok { fmt.Println("Dummy!!") return } fmt.Printf("知らない型 %v, %T\n", i, i) } func TypeSwitchFunction(i interface{}) { switch t := i.(type) { case Person: t.Greet() case Dummy: fmt.Println("Dummy!!") default: fmt.Printf("知らない型 %v, %T\n", t, t) } } func main() { // Person NotTypeSwitchFunction(Person{"Tanaka"}) TypeSwitchFunction(Person{"Hoge"}) // int NotTypeSwitchFunction(10) TypeSwitchFunction(100) }
実行するとこんな感じ。
Tanaka Hoge 知らない型 10, int 知らない型 100, int
ToString?
他の言語でオブジェクトの文字列表現を返す ToString/toString というメソッドですが、Go では fmt パッケージの Stringers インターフェースがそれにあたるみたいです。String メソッドがあるだけのインターフェース。
ということで、文字列として扱いたい場合には String メソッドを持ってれば OK。ここでもポインターに String メソッドを定義したら悲しいことが起きるケースがあります。
package main import"fmt"type Person struct { Name string } func (p *Person) String() string { if p == nil { return"<nil>" } return p.Name } func main() { p := Person{"Tanaka"} fmt.Println(p) // {Tanaka} fmt.Println(&p) // Tanaka }
まぁ、こういうことですよね。
package main import"fmt"type Person struct { Name string } func (p *Person) String() string { if p == nil { return"<nil>" } return p.Name } func main() { p := Person{"Tanaka"} i := interface{}(p) s, ok := i.(fmt.Stringer) fmt.Printf("ok = %v, s = %v\n", ok, s) // ok = false, s = <nil> i = interface{}(&p) s, ok = i.(fmt.Stringer) fmt.Printf("ok = %v, s = %v\n", ok, s) // ok = true, s = Tanaka }
ここらへん Go のはまりどころになりそうな気がするけどどうだろう。因みにポインターとポインターじゃないものを受け取るメソッドを両方定義するとエラーになるので注意。どっちかに寄せるのがよさそう。
type Person struct { Name string } func (p *Person) String() string { if p == nil { return"<nil>" } return p.Name } func (p Person) String() string { // error!!if p == nil { return"<nil>" } return p.Name }
エラー処理
最初にびっくりしたことの 1 つとして例外という仕組みが Go 言語にはないです。
えっ!?じゃぁどうするの!?というのですが戻り値で返すというのが方針みたいです。
C# でも例外は、もう取り返しがつかないエラーに対して使って、それ以外は戻り値などを使うべしというガイドラインがあるので、それなら例外いらんくない?って感じみたいですね。まだやってないので詳しくは知らないですが、たびたび panic というのが出てくるので、これが致命的に取り返しのつかないエラーに該当するものっぽい。
エラーを表すインターフェースが定義されてる。こんな感じで
typeerrorinterface { Error() string }
つまり、これを実装してればエラー。そして Go では戻り値の 2 つ目にエラーの有無を返すのが一般的みたい。つまりこんな感じ。
package main import"fmt"type DivideZeroError struct { // 何かエラーについて有益な情報を持たせるときはここに定義 } // error#Error() の実装func (e *DivideZeroError) Error() string { return"0 で割らないで" } func div(x, y int) (int, error) { if y == 0 { return0, &DivideZeroError{} } return x / y, nil } func main() { // OK caseif answer, e := div(10, 3); e == nil { fmt.Printf("正常: %v\n", answer) } else { fmt.Printf(e.Error()) } // Error caseif answer, e := div(10, 0); e == nil { fmt.Printf("正常: %v\n", answer) } else { fmt.Printf(e.Error()) } }
実行するとこんな感じ
正常: 30で割らないで
複数のタイプのエラーを返すような場合は型スイッチが便利そうかな?こんな感じで。
package main import"fmt"type DivideZeroError struct { } type LargeNumberError struct { Detail string } // error#Error() の実装func (e *DivideZeroError) Error() string { return"0 で割らないで" } func (e *LargeNumberError) Error() string { return"数字が大きすぎ" } func div(x, y int) (int, error) { if y == 0 { return0, &DivideZeroError{} } if x >= 100 || y >= 100 { return0, &LargeNumberError{"100 以上はちょっと…"} } return x / y, nil } func main() { // OK caseswitch answer, e := div(10, 3); t := e.(type) { casenil: fmt.Printf("OK: %v\n", answer) case *DivideZeroError: fmt.Println(t.Error()) case *LargeNumberError: fmt.Println(t.Error(), t.Detail) } // LargeNumberError caseswitch answer, e := div(100, 3); t := e.(type) { casenil: fmt.Printf("OK: %v\n", answer) case *DivideZeroError: fmt.Println(t.Error()) case *LargeNumberError: fmt.Println(t.Error(), t.Detail) } // DivideZeroError caseswitch answer, e := div(10, 0); t := e.(type) { casenil: fmt.Printf("OK: %v\n", answer) case *DivideZeroError: fmt.Println(t.Error()) case *LargeNumberError: fmt.Println(t.Error(), t.Detail) } }
書いてて思ったけど戻り値で一旦受けて if で nil チェックしてエラーがあったら型スイッチのほうが自然に感じる。
package main import"fmt"type DivideZeroError struct { } type LargeNumberError struct { Detail string } // error#Error() の実装func (e *DivideZeroError) Error() string { return"0 で割らないで" } func (e *LargeNumberError) Error() string { return"数字が大きすぎ" } func div(x, y int) (int, error) { if y == 0 { return0, &DivideZeroError{} } if x >= 100 || y >= 100 { return0, &LargeNumberError{"100 以上はちょっと…"} } return x / y, nil } func main() { // OK case answer, e := div(10, 3) if e == nil { // correct fmt.Printf("OK: %v\n", answer) } else { switch t := e.(type) { case *DivideZeroError: fmt.Println(t.Error()) case *LargeNumberError: fmt.Println(t.Error(), t.Detail) default: fmt.Printf("Unknown error: %v, %T", t, t) } } }
入出力
io パッケージを使う。io.Reader
インターフェースがあっていろんなところで使われてる。Java や C# でいうところの Stream に通じるものがありそう。
io.Reader
インターフェースは Read
メソッドを提供してる。
// b に読み込んだデータが入る、n に読み込んだバイト数、err にエラー。終端に来たら io.EOF が err に入る fnc (T) Read(b []byte) (n int, err error)
つまり最後まで読み込むときは io.EOF
になるまで無限ループすればいい。これは A Tour of Go で示されてるコードをそのまま写経
package main import ( "fmt""io""strings" ) func main() { r := strings.NewReader("Hello world") buffer := make([]byte, 8) for { n, err := r.Read(buffer) fmt.Printf("n = %v, err = %v, buffer = %v\n", n, err, buffer) // そのま情報を出力 fmt.Printf("%q\n", buffer[:n]) // 読み取ったデータだけ出力if err == io.EOF { break } } }
結果はこんな感じ
n = 8, err = <nil>, buffer = [72 101 108 108 111 32 119 111] "Hello wo" n = 3, err = <nil>, buffer = [114 108 100 108 111 32 119 111] "rld" n = 0, err = EOF, buffer = [114 108 100 108 111 32 119 111] ""
スライスこんな感じに使うんだって思った。あと %q
っていうのもあるんだ。
画像
唐突に画像を扱う方法が紹介されてた。image パッケージにある Image インターフェースを使う。
package image type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
これさえ実装すれば画像として振る舞えるってお手軽そう。ここも A Tour of Go のコードを写経しとく。
package main import ( "fmt""image" ) func main() { m := image.NewRGBA(image.Rect(0, 0, 100, 100)) fmt.Println(m.Bounds()) fmt.Println(m.At(0, 0).RGBA()) }
実行するとこんな感じ
(0,0)-(100,100) 0 0 0 0
RGBA 関数は r, g, b, a uint32 を返す。なので、これも実質同じ。
package main import ( "fmt""image" ) func main() { m := image.NewRGBA(image.Rect(0, 0, 100, 100)) fmt.Println(m.Bounds()) r, g, b, a := m.At(0, 0).RGBA() fmt.Println(r, g, b, a) }
Goroutines
続けて Concurrency を見ていく。
ゴルーチンというらしい。軽量なスレッド。go 関数呼び出し
で裏側で処理を動かせる。
package main import ( "fmt""time" ) func HeavyProcess(s string) { fmt.Printf("before: %v\n", s) time.Sleep(1000) fmt.Printf("after: %v\n", s) } func main() { // 裏で動くgo HeavyProcess("first") go HeavyProcess("second") // ここで動く HeavyProcess("main") time.Sleep(2500) }
実行するとこうなった。きっと実行のたびに違う結果になる。
before: second before: main before: first after: main after: second after: first
裏側と呼び出し側での値の送受信はチャネルというのを使う。chan 型名
でチャネルになる。make を使って作ってチャネルオペレーターの <-
を使ってチャネルに値を送ったりチャネルから値を取ったりする。
ch := make(chanint) // チャネルを作って ch <- 10// チャネルに値を送って i := <-ch // チャネルから値を受け取る
同じスレッドで ch <- 10
と i := <-ch
をやるとデッドロックと言われる。実際に試してみたコードはこんな感じ。結果は 10 と出るだけ。
package main import"fmt"func main() { // チャネルを作る ch := make(chanint) // ゴルーチンを作って、その先でチャネルに値を送信gofunc() { ch <- 10 }() // ゴルーチンで値が渡ってくるのを待って値を受け取る i := <-ch // 出力 fmt.Println(i) // 10 }
別に <-
は一回というわけではなく複数回も OK
package main import"fmt"func main() { // チャネルを作る ch := make(chanint) // ゴルーチンを作って、その先でチャネルに値を送信gofunc() { ch <- 10 }() gofunc() { ch <- 20 }() // ゴルーチンで値が渡ってくるのを待って値を受け取る i, j := <-ch, <-ch // 出力 fmt.Println(i, j) }
実行すると 10 20
か 20 10
と表示されると思うんだけど、何回実行しても 20 10
だった。
チャネルには複数の値を突っ込めるけど、沢山詰め込まれたらブロックするようにバッファーを指定できる。make 関数の第二引数にバッファーのサイズを指定する。
package main import"fmt"func main() { // チャネルを作る ch := make(chanint, 2) // バッファーサイズは 2// 2 回値を送るのは OK ch <- 10 ch <- 20// 1 つずつ取り出して表示 fmt.Println(<-ch) fmt.Println(<-ch) }
どうもバッファーのサイズを指定しないとバッファーのサイズが 0 と同じ動きをするのでそういうことなのだろう。
因みにバッファーサイズを 2 にして ch <- xx
を 3 回やると…
package main import"fmt"func main() { // チャネルを作る ch := make(chanint, 2) // バッファーサイズは 2// 3 回値を送るのは NG ch <- 10 ch <- 20 ch <- 30// 1 つずつ取り出して表示 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) }
deadlock! エラーになる。
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() c:/Users/kaota/go/src/sample/main.go:11 +0xa2 exit status 2
チャネルを close することで受け手側が、もうチャネルにデータが来ないことを判別できる。close したチャネルにデータ送信すると panic になる。チャネルは close しなくても何も不都合はないので、データが終わるということを明示するときに close する。
package main import"fmt"func main() { // チャネルを作る ch := make(chanint) // 3 つデータを送って closegofunc() { ch <- 10 ch <- 20 ch <- 30close(ch) }() // 無限ループでデータが終わるまで読み込むfor { v, ok := <-ch if !ok { break } fmt.Println(v) } }
実行すると思った通りの結果になるはず
10 20 30
因みにチャネルに値がある限り実行するというのは range を使って以下のように書ける。普通はこっちのほうがいいと思う。
package main import"fmt"func main() { // チャネルを作る ch := make(chanint) // 3 つデータを送って closegofunc() { ch <- 10 ch <- 20 ch <- 30close(ch) }() // range を使うとすっきりfor v := range ch { fmt.Println(v) } }
select case は、チャネルに対する操作を case に記述すると準備ができたものを選んで実行する。複数同時に準備が出来てる場合はランダムに実行される。 例が難しい…。
package main import ( "fmt" ) func main() { // チャネルを作る out := make(chanint) quit := make(chanint) // 3 つデータを送って quitgofunc() { fmt.Println("groutine started") out <- 10 out <- 20 out <- 30 quit <- 0 fmt.Println("groutine ended") }() for { endLoop := falseselect { case x := <-out: fmt.Println(x) case<-quit: // quit にデータが来たら終わりのフラグを立てる fmt.Println("Done!!") endLoop = true } if endLoop { break } } fmt.Println("main endied") }
実行するとこんな感じ。
groutine started 10 20 30 Done!! main endied
default が無いと、case のどれかが準備 OK になるまでブロックされるけど default があるとブロックされない。
default が無いケース
package main import ( "fmt""time" ) func main() { // チャネルを作る out := make(chanint) quit := make(chanint) // 少し間隔をあけてデータを 3 つ送った後 quitgofunc() { fmt.Println(time.Now(), "groutine started") out <- 10 time.Sleep(10) out <- 20 time.Sleep(10) out <- 30 time.Sleep(10) quit <- 0 fmt.Println(time.Now(), "groutine ended") }() for { endLoop := falseselect { case x := <-out: fmt.Printf("%v: %v\n", time.Now(), x) case<-quit: fmt.Println(time.Now(), " Done!!") endLoop = true } if endLoop { break } } fmt.Println(time.Now(), "main endied") }
実行するとこんな感じ。
2018-10-21 20:20:15.7192278 +0900 JST m=+0.003009901 groutine started 2018-10-21 20:20:15.7682091 +0900 JST m=+0.051991301: 10 2018-10-21 20:20:15.7701806 +0900 JST m=+0.053962801: 20 2018-10-21 20:20:15.7712797 +0900 JST m=+0.055061901: 30 2018-10-21 20:20:15.7731815 +0900 JST m=+0.056963701 groutine ended 2018-10-21 20:20:15.7731815 +0900 JST m=+0.056963701 Done!! 2018-10-21 20:20:15.7731815 +0900 JST m=+0.056963701 main endied
ちゃんと待ってる。default を追加してみる。
package main import ( "fmt""time" ) func main() { // チャネルを作る out := make(chanint) quit := make(chanint) // 少し間隔をあけてデータを 3 つ送った後 quitgofunc() { fmt.Println(time.Now(), "groutine started") out <- 10 time.Sleep(10) out <- 20 time.Sleep(10) out <- 30 time.Sleep(10) quit <- 0 fmt.Println(time.Now(), "groutine ended") }() for { endLoop := falseselect { case x := <-out: fmt.Printf("%v: %v\n", time.Now(), x) case<-quit: fmt.Println(time.Now(), "Done!!") endLoop = truedefault: fmt.Println(time.Now(), "no data yet") } if endLoop { break } } fmt.Println(time.Now(), "main endied") }
実行するとこうなる。待たないのでデータが無いというログが沢山でる。
2018-10-21 20:22:08.3111922 +0900 JST m=+0.004003201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.3581872 +0900 JST m=+0.050998201 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.359187 +0900 JST m=+0.051998001 no data yet 2018-10-21 20:22:08.3601888 +0900 JST m=+0.052999801 no data yet 2018-10-21 20:22:08.3601888 +0900 JST m=+0.052999801 no data yet 2018-10-21 20:22:08.3601888 +0900 JST m=+0.052999801 no data yet 2018-10-21 20:22:08.3601888 +0900 JST m=+0.052999801 no data yet 2018-10-21 20:22:08.3611868 +0900 JST m=+0.053997801 no data yet 2018-10-21 20:22:08.3611868 +0900 JST m=+0.053997801 no data yet 2018-10-21 20:22:08.3611868 +0900 JST m=+0.053997801 no data yet 2018-10-21 20:22:08.3611868 +0900 JST m=+0.053997801 no data yet 2018-10-21 20:22:08.3611868 +0900 JST m=+0.053997801 no data yet 2018-10-21 20:22:08.3621869 +0900 JST m=+0.054997901 no data yet 2018-10-21 20:22:08.3621869 +0900 JST m=+0.054997901 no data yet 2018-10-21 20:22:08.3621869 +0900 JST m=+0.054997901 no data yet 2018-10-21 20:22:08.3621869 +0900 JST m=+0.054997901 no data yet 2018-10-21 20:22:08.3631903 +0900 JST m=+0.056001301 no data yet 2018-10-21 20:22:08.3631903 +0900 JST m=+0.056001301 no data yet 2018-10-21 20:22:08.3631903 +0900 JST m=+0.056001301 no data yet 2018-10-21 20:22:08.3631903 +0900 JST m=+0.056001301 no data yet 2018-10-21 20:22:08.3631903 +0900 JST m=+0.056001301 no data yet 2018-10-21 20:22:08.3641871 +0900 JST m=+0.056998101 no data yet 2018-10-21 20:22:08.3641871 +0900 JST m=+0.056998101 no data yet 2018-10-21 20:22:08.3641871 +0900 JST m=+0.056998101 no data yet 2018-10-21 20:22:08.3641871 +0900 JST m=+0.056998101 no data yet 2018-10-21 20:22:08.3641871 +0900 JST m=+0.056998101 no data yet 2018-10-21 20:22:08.365188 +0900 JST m=+0.057999001 no data yet 2018-10-21 20:22:08.365188 +0900 JST m=+0.057999001 no data yet 2018-10-21 20:22:08.365188 +0900 JST m=+0.057999001 no data yet 2018-10-21 20:22:08.365188 +0900 JST m=+0.057999001 no data yet 2018-10-21 20:22:08.365188 +0900 JST m=+0.057999001 no data yet 2018-10-21 20:22:08.3661872 +0900 JST m=+0.058998201 no data yet 2018-10-21 20:22:08.3661872 +0900 JST m=+0.058998201 no data yet 2018-10-21 20:22:08.368188 +0900 JST m=+0.060999001 no data yet 2018-10-21 20:22:08.3111922 +0900 JST m=+0.004003201 groutine started 2018-10-21 20:22:08.368188 +0900 JST m=+0.060999001 no data yet 2018-10-21 20:22:08.3691896 +0900 JST m=+0.062000601: 10 2018-10-21 20:22:08.3691896 +0900 JST m=+0.062000601 no data yet 2018-10-21 20:22:08.3691896 +0900 JST m=+0.062000601 no data yet 2018-10-21 20:22:08.3691896 +0900 JST m=+0.062000601 no data yet 2018-10-21 20:22:08.3701879 +0900 JST m=+0.062998901 no data yet 2018-10-21 20:22:08.3701879 +0900 JST m=+0.062998901 no data yet 2018-10-21 20:22:08.3701879 +0900 JST m=+0.062998901 no data yet 2018-10-21 20:22:08.3701879 +0900 JST m=+0.062998901 no data yet 2018-10-21 20:22:08.3711933 +0900 JST m=+0.064004301: 20 2018-10-21 20:22:08.3711933 +0900 JST m=+0.064004301 no data yet 2018-10-21 20:22:08.3711933 +0900 JST m=+0.064004301 no data yet 2018-10-21 20:22:08.3711933 +0900 JST m=+0.064004301 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.372192 +0900 JST m=+0.065003001 no data yet 2018-10-21 20:22:08.3731933 +0900 JST m=+0.066004301: 30 2018-10-21 20:22:08.3731933 +0900 JST m=+0.066004301 no data yet 2018-10-21 20:22:08.3731933 +0900 JST m=+0.066004301 no data yet 2018-10-21 20:22:08.3731933 +0900 JST m=+0.066004301 no data yet 2018-10-21 20:22:08.3731933 +0900 JST m=+0.066004301 no data yet 2018-10-21 20:22:08.3741897 +0900 JST m=+0.067000701 no data yet 2018-10-21 20:22:08.3741897 +0900 JST m=+0.067000701 no data yet 2018-10-21 20:22:08.3741897 +0900 JST m=+0.067000701 no data yet 2018-10-21 20:22:08.3741897 +0900 JST m=+0.067000701 no data yet 2018-10-21 20:22:08.3751896 +0900 JST m=+0.068000601 Done!! 2018-10-21 20:22:08.3751896 +0900 JST m=+0.068000601 main endied
sync.Mutex
を使うことでロックしたりアンロックしたりすることが出来る。他の言語のものと大体同じ。
まずは Mutex 無しで。複数スレッドから1000回インクリメントして1000回デクリメントするだけのプログラム。
package main import ( "fmt""time" ) type Counter struct { value int } func (c *Counter) Incr() { c.value++ } func (c *Counter) Decr() { c.value-- } func (c *Counter) Value() int { return c.value } func main() { c := Counter{} for i := 0; i < 1000; i++ { go c.Incr() go c.Decr() } time.Sleep(time.Second) fmt.Println(c.Value()) }
実行すると、その時々で変わるけど大体 0 にならない。例えば今実行したら 1 になった。もう一回実行すると -5 になった。 Inc, Dec, Value メソッドが同時に 1 つしか実行されないように Mutex を使うようにしてみた。ロックされっぱなしは困るので、ここでは defer を使って確実にメソッドの終わりで Unlock されるようにしてる。
package main import ( "fmt""sync""time" ) type Counter struct { value int mutex sync.Mutex } func (c *Counter) Incr() { c.mutex.Lock() defer c.mutex.Unlock() c.value++ } func (c *Counter) Decr() { c.mutex.Lock() defer c.mutex.Unlock() c.value-- } func (c *Counter) Value() int { c.mutex.Lock() defer c.mutex.Unlock() return c.value } func main() { c := Counter{} for i := 0; i < 1000; i++ { go c.Incr() go c.Decr() } time.Sleep(time.Second) fmt.Println(c.Value()) }
何回実行しても 0 になる。期待した通り。
A Tour of Go を終えて
次は以下のドキュメントや動画がお勧めされてる。どれ見ようかなぁ。
- Writing, building, installing, and testing Go code - YouTube
- Documentation - The Go Programming Language
- How to Write Go Code - The Go Programming Language
- Packages - The Go Programming Language
- The Go Programming Language Specification - The Go Programming Language
- Google I/O 2012 - Go Concurrency Patterns - YouTube
- Google I/O 2013 - Advanced Go Concurrency Patterns - YouTube
- Share Memory By Communicating - The Go Programming Language
- Go: a simple programming environment on Vimeo
- Writing Web Applications - The Go Programming Language