- 投稿日:2020-09-09T23:03:36+09:00
身に覚えのない"invalid character 'ã' looking for beginning of value"の原因
TL;DR
json.Unmarshal
でinvalid character 'ã' looking for beginning of valueというエラーが出た時は、JSON文字列に全角スペースが紛れている可能性大!!
スペースが全角になっている{"name": "Taro"}背景
APIサーバ実装中に上記のエラー発生。「
a
がなんで不正やねん」と思いがっつりハマったので備忘録として書きました。
(※実際にはチルダのついたã
です)環境
- Go 1.14
サンプルコード
main.gopackage main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` } func main() { // コロンの直後に全角が! jsonStr := `{"name": "Taro"}` person := Person{} err := json.Unmarshal([]byte(jsonStr), &person) if err != nil { fmt.Println(err.Error()) // invalid character 'ã' looking for beginning of value } }なぜエラーに'ã'が出てくるのか
全角文字の先頭バイト
227
に対応するutf-8の文字がã
だからです。
Unmarshal
には[]byte
を渡しているので、全角文字の先頭バイトã
を読み込んだ時点でパースエラーになったというのが原因でした。fmt.Println([]byte(" ")) // [227 128 128] fmt.Println(string(227)) // ã
encoding/json
の実装を見ても、1バイトずつ読み込んで、スキャナが解釈できないバイトが見つかった時点でエラーを吐いていることが分かります。
Unmarshalの定義
checkValid(Unmarshal内部でJSON形式チェックに使用)の定義最後に
全角スペースはちゃんとハイライトしよう!
- 投稿日:2020-09-09T22:27:01+09:00
Golangの'nil slice'と'non-nil slice'で地味に詰まった話
Golangでテストコードを書いていた際に、地味に詰まったことがあったので共有。
サンプルコード
intのスライスを引数として受け取り、その中から正の整数だけを返す関数をサンプルとして用意しました。
example.gopackage example // Example は、テスト用に作成した正の整数だけを返す関数 func Example(nums []int) (ret []int) { for _, num := range nums { if num > 0 { ret = append(ret, num) } } return }この関数に対して、下記の様なテストコードを書きます。
(本当はもっとテストケースを用意するべきだと思います。)example_test.gopackage example import ( "testing" "github.com/stretchr/testify/assert" ) func TestExample(t *testing.T) { tests := []struct { input []int expected []int }{ { input: []int{1, -2, 3}, expected: []int{1, 3}, }, { input: []int{-1, -2, -3}, expected: []int{}, }, } for _, test := range tests { result := Example(test.input) assert.Equal(t, result, test.expected) } }そして、このテストを実行してみると、下記のログが表示されテストは失敗します。
$ go test --run TestExample --- FAIL: TestExample (0.00s) example_test.go:27: Error Trace: example_test.go:27 Error: Not equal: expected: []int(nil) actual : []int{} Diff: --- Expected +++ Actual @@ -1,2 +1,3 @@ -([]int) <nil> +([]int) { +} Test: TestExample FAIL exit status 1どうやら、
期待している値(expected)が、([]int) <nil>
になっているのに対して、
実際に返される値(result)は、([]int) {}
となっているため、テストが失敗しているようです。nil sliceとnon-nil slice
このテスト失敗の理由を説明するためには、lengthとcapacityが共に0のスライスとして、
nil slice
とnon-nil slice
というものが存在することを説明する必要があります。この2つのスライスは、見た目上ほとんど同じ挙動なのですが、
その名の通りnilかnilでないかという違いがあります。package main import "fmt" func main() { // nil sliceになる var nSlice []int // non-nil sliceになる eSlice := []int{} eSlice2 := make([]int, 0) fmt.Println(len(nSlice), cap(nSlice), nSlice == nil) fmt.Println(len(eSlice), cap(eSlice), eSlice == nil) fmt.Println(len(eSlice2), cap(eSlice2), eSlice2 == nil) }出力0 0 true 0 0 false 0 0 falseテストの失敗の原因
Goの名前付き戻り値にsliceを指定して、何も値を代入しないままreturnした場合、nil sliceを返します。
サンプルコードpackage main import ( "fmt" ) func main() { slice := example() fmt.Println(len(slice), cap(slice), slice == nil) } func example () (slice []int) { return }最初にお見せしたテストでは、example関数は
nil slice
を返すにもかかわらず、
non-nil slice
と比較するようなテストになってしまっていたため、テストが失敗してしまったのです。シンプルなミスであるにもかかわらず、
nil sliceとnon-nil sliceのことを自分があまり意識していなかったために、
原因に気づくまでに少し時間がかかってしまいました。反省です。まとめ
- lengthとcapacityが共に0であるsliceには、nilとnon-nilの2種類がある。
- 名前付き戻り値で、sliceをそのままreturnすると、nil sliceになる。
- 投稿日:2020-09-09T17:52:28+09:00
Goでsliceをpoolするときの罠
sync.Poolのすすめ
Goの高速化テクニックの一つに
sync.Pool
を使うというものがあります. bufferとして使う領域を毎回allocせずに使いまわすことで, allocとGCにかかる時間を省くのが狙いです.poolが効果を発揮するのは可変長データをgo routineで並列処理する場合です.
予め適切なbuffer sizeがわからない場合でも, 足りなくなったら都度交換することで, 十分な時間が経てばpool内は大きなbufferだけが残る事になり, 償却的にzero allocにできます.// pool初期化 pool := sync.Pool { New: func() interface{} { return &Buffer{} }, } //... // 使うとき buf := pool.Get().(*Buffer) buf.Reset() if !buf.EnoughSize() { buf = NewLargerBuffer() //bufferが足りないので大きいbufferに取り替える } pool.Put(buf)ここで古い小さなbufferをpoolに戻さないところがポイントです.
例えばhttp handlerでzero allocを達成できれば, webサーバーの高速化にもかなり貢献するはずです.
sliceをpoolする
bufferとしてsliceを使うユースケースは多いと思いますが, sliceをpoolに突っ込んだ場合, zero allocを達成するまでにいくつか罠を踏んだので書いておきます.
slice実体をpoolする(罠1)
sliceをpoolしようとして, まず私はこう書きました. poolは[]intを貯めています.
func BenchmarkSlicePool(b *testing.B) { pool := sync.Pool{ New: func() interface{} { return make([]int, 0, 0) // 適切なsizeがわからない場合を想定してあえてcap = 0とした. }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { arr := pool.Get().([]int)[0:0] // キャストしてlenリセット arr = append(arr, 42) pool.Put(arr) } }) }slice = ポインタという感覚からすると正しいように思えますが, しかしこれはzero allocにはなりません.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePool 12 100 28.2 ns/op 32 B/op 1 allocs/op このallocは
pool.Put(arr)
から来ています.
Putの引数はinterface{}型なので, sliceをinterface{}にキャストしますがそのときこの中でslice headerをheapにコピーする必要があります.実際にキャストの内部を見てみると...
src/runtime/iface.go380 func convTslice(val []byte) (x unsafe.Pointer) { 381 // Note: this must work for any element type, not just byte. 382 if (*slice)(unsafe.Pointer(&val)).array == nil { 383 x = unsafe.Pointer(&zeroVal[0]) 384 } else { 385 x = mallocgc(unsafe.Sizeof(val), sliceType, true) 386 *(*[]byte)(x) = val 387 } 388 }なお, slice headerはsliceの内部表現で, 下記の通りptr, len, capを持つstructです.
type SliceHeader struct { Data uintptr Len int Cap int }sliceのポインタをpoolする(罠2)
OK. じゃあポインタにすっかという事で,
return &make([]int)
がsyntax errorになる事に若干イライラしつつ, こうします. 今度は*[]intを貯めています.func BenchmarkSlicePtrPool_1(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { arr := (*pool.Get().(*[]int))[0:0] // キャストしてderefしてlenリセット arr = append(arr, 42) pool.Put(&arr) } }) }しかしこれもzero allocならず.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_1 12 100 27.0 ns/op 32 B/op 1 allocs/op escape解析をしてみると, Getの行でarrがheapに行ってしまっていることがわかります.
$ go test . -gcflags="-m" ... moved to heap: arrPutでheapに行くことがわかっているので, arrをstackに確保しなかったということですね.
sliceのポインタをpoolし, ポインタを残す(罠3)
arrをstackに確保するべく, 次の手は
func BenchmarkSlicePtrPool_2(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { ptr := pool.Get().(*[]int) // キャスト arr := *ptr // deref arr = arr[0:0] // lenリセット arr = append(arr, 42) pool.Put(ptr) } }) }これもzero allocならず.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_2 12 100 12.4 ns/op 8 B/op 1 allocs/op 今度は, appendでせっかくgrowしたarrがPutできていません. Putの時点で
ptr != &arr
であり, ptrの指すsliceは依然としてcap == 0
のため, 毎回growしてしまいます.sliceのポインタをpoolし, ポインタを残し, Put前にheaderをコピーする (多分これが一番速いと思います)
Putする前に, ptrが指すheap上のslice headerにstack上のslice header (arr)をコピーします.
func BenchmarkSlicePtrPool_3(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { ptr := pool.Get().(*[]int) arr := *ptr arr = arr[0:0] arr = append(arr, 42) *ptr = arr //slice headerの書き戻し pool.Put(ptr) } }) }これでようやく zero alloc達成できました.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_3 12 100 3.39 ns/op 0 B/op 0 allocs/op BenchmarkSlicePool (再掲) 12 100 28.2 ns/op 32 B/op 1 allocs/op slice実体のpoolから比べると8.3x速くなっています. 配列の領域自体は使い回しできていても, 高々32Bのallocでも馬鹿にできないことがわかります.
検証環境
go version go1.15.1 darwin/amd64
Mac Pro (Late 2013)
3.5 GHz 6-Core Intel Xeon E5
- 投稿日:2020-09-09T17:41:48+09:00
Goでslice poolを使うときの罠
sync.Poolのすすめ
Goの高速化テクニックの一つに
sync.Pool
を使うというものがあります. bufferとして使う領域を毎回allocせずに使いまわすことで, allocとGCにかかる時間を省くのが狙いです.poolが効果を発揮するのは可変長データをgo routineで並列処理する場合です.
予め適切なbuffer sizeがわからない場合でも, 足りなくなったら都度交換することで, 十分な時間が経てばpool内は大きなbufferだけが残る事になり, 償却的にzero allocにできます.// pool初期化 pool := sync.Pool { New: func() interface{} { return &SmallBuffer{} }, } //... // 使うとき buf := pool.Get().(*Buffer) buf.Reset() if !buf.EnoughSize() { buf = NewLargerBuffer() //bufferが足りないので大きいbufferに取り替える } pool.Put(buf)ここで古い小さなbufferをpoolに戻さないところがポイントです.
例えばhttp handlerでzero allocを達成できれば, サーバーの高速化にかなり貢献するはずです.
sliceをpoolする
bufferとしてsliceをユースケースとして使うことは多いと思いますが, sliceをpoolに突っ込んだ場合, zero allocを達成するまでにいくつか罠を踏んだので書いておきます.
slice実体をpoolする(罠1)
sliceをpoolしようとして, まず私はこう書きました. poolは[]intを貯めています.
func BenchmarkSlicePool(b *testing.B) { pool := sync.Pool{ New: func() interface{} { return make([]int, 0, 0) // 適切なsizeがわからない場合を想定してあえてcap = 0とした. }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { arr := pool.Get().([]int)[0:0] // キャストしてlenリセット arr = append(arr, 42) pool.Put(arr) } }) }slice = ポインタという感覚からすると正しいように思えますが, しかしこれはzero allocにはなりません.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePool 12 100 28.2 ns/op 32 B/op 1 allocs/op このallocは
pool.Put(arr)
から来ています.
Putの引数はinterface{}型なので, sliceをinterface{}にキャストしますがそのときこの中でslice headerをheapにコピーする必要があります.実際にキャストの内部を見てみると...
src/runtime/iface.go380 func convTslice(val []byte) (x unsafe.Pointer) { 381 // Note: this must work for any element type, not just byte. 382 if (*slice)(unsafe.Pointer(&val)).array == nil { 383 x = unsafe.Pointer(&zeroVal[0]) 384 } else { 385 x = mallocgc(unsafe.Sizeof(val), sliceType, true) 386 *(*[]byte)(x) = val 387 } 388 }なお, slice headerはsliceの内部表現で, 下記の通りptr, len, capを持つstructです.
type SliceHeader struct { Data uintptr Len int Cap int }sliceのポインタをpoolする(罠2)
OK. じゃあポインタにすっかという事で,
return &make([]int)
がsyntax errorになる事に若干イライラしつつ, こうします. 今度は*[]intを貯めています.func BenchmarkSlicePtrPool_1(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { arr := (*pool.Get().(*[]int))[0:0] // キャストしてderefしてlenリセット arr = append(arr, 42) pool.Put(&arr) } }) }しかしこれもzero allocならず.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_1 12 100 27.0 ns/op 32 B/op 1 allocs/op escape解析をしてみると, Getの行でarrがheapに行ってしまっていることがわかります.
$ go test . -gcflags="-m" ... moved to heap: arrPutでheapに行くことがわかっているので, arrをstackに確保しなかったということですね.
sliceのポインタをpoolし, ポインタを残す(罠3)
arrをstackに確保するべく, 次の手は
func BenchmarkSlicePtrPool_2(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { ptr := pool.Get().(*[]int) // キャスト arr := *ptr // deref arr = arr[0:0] // lenリセット arr = append(arr, 42) pool.Put(ptr) } }) }これもzero allocならず.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_2 12 100 12.4 ns/op 8 B/op 1 allocs/op 今度は, appendでせっかくgrowしたarrがPutできていません. Putの時点で
ptr != &arr
であり, ptrの指すsliceは依然としてcap == 0
のため, 毎回growしてしまいます.sliceのポインタをpoolし, ポインタを残し, Put前にheaderをコピーする (多分これが一番速いと思います)
Putする前に, ptrが指すheap上のslice headerにstack上のslice header (arr)をコピーします.
func BenchmarkSlicePtrPool_3(b *testing.B) { pool := sync.Pool{ New: func() interface{} { s := make([]int, 0, 0) return &s }, } b.RunParallel(func(pb *testing.PB) { for pb.Next() { ptr := pool.Get().(*[]int) arr := *ptr arr = arr[0:0] arr = append(arr, 42) *ptr = arr //slice headerの書き戻し pool.Put(ptr) } }) }これでようやく zero alloc達成できました. なお,
ptr == &arr
の場合は代入を省けますが, 条件分岐する方が遅かったです.
bench name cores parallelism ns / op alloc bytes / op alloc times / op BenchmarkSlicePtrPool_3 12 100 3.39 ns/op 0 B/op 0 allocs/op slice実体のpoolから比べると8.3x速くなっています. 配列の領域をコピーしていなくても, 高々32Bのallocでも馬鹿にできないことがわかります.
検証環境
go version go1.15.1 darwin/amd64
Mac Pro (Late 2013)
3.5 GHz 6-Core Intel Xeon E5
- 投稿日:2020-09-09T09:51:18+09:00
【超簡単】GoのインストールとVSCode設定方法
インストール
インストール
Mac
以下のコマンドを実行します。
$ brew install goWindows
以下のページより、Windows用のインストーラー(.msiファイル)をダウンロードして実行します。
Download and installインストーラーに表示される手順に従って進めます。
インストール先フォルダは、特段の理由がなければデフォルトのまま変更しない方がいいです。インストール後確認
インストール後、コマンドラインで以下を実行して動作すればOK。
$ go version go version go1.15.1 windows/amd64環境変数の設定
PATHに
%GOPATH%\bin
を登録します。
PATHに登録しておかないとgo get
でインストールしたコマンドを実行することができません。Mac
以下のコマンドを実行して設定します。
$ echo "export GOPATH=$(go env GOPATH)" >> ~/.bash_profile $ echo "export PATH=$PATH:$(go env GOPATH)/bin" >> ~/.bash_profile $ source ~/.bash_profileWindows
環境変数の設定がわからない場合は、以下が参考になります。
WindowsにGo言語をインストールする方法まとめVSCode Extension
VSCodeをインストールしていない場合は、こちらからインストールします。
インストール
VSCodeを起動し、拡張機能(Ctrl+Shift+X)を開いて
Go
で検索します。
検索結果に表示される以下をインストールします。主な機能
- Lint&Format
- Debug
- コード補完
Tips
VSCodeで開発をしていて、補完が効かない、エラーが消えないなどの時は、VSCode上で以下のコマンドを実行するとたいてい解決します。
Ctrl + Shift + P
でコマンドパレットを表示Go: Restart Language Server
と入力して実行プロジェクトのはじめかた・おすすめライブラリ
手前みそですが、以下の記事を参考にして頂けると幸いです。
Goプロジェクトのはじめかたとおすすめライブラリ8.5選。ひな形にも使えるサンプルもあるよ。
⇒Qiitaのデイリートレンド5位に入りました!さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
Goプロジェクトのはじめかたやおすすめライブラリの記事に続き、GoインストールとVSCode設定に関してもQiitaに投稿しました?
— やぎぬ?行動力エンジニア (@yagi_eng) September 9, 2020
他言語と比べて、圧倒的に簡単に始められるのがGoのいいところです!
Goをこれから始める方など、良かったら参考にしてください?https://t.co/s02NmXTfU3
- 投稿日:2020-09-09T00:24:34+09:00
ヒヨコとはじめる、やさしいGo言語の並行処理
こんばんは、ねじねじおです。
Goの並行処理をヒヨコと一緒に学んでいきます。
今回のポイントは3つだけです。
1. goroutine で並行処理を実行する
2. WaitGroup で処理の終了を待つ
3. channel でキューをつくるここに10匹の泣いているヒヨコを慰めるプログラムを用意しました。このプログラムでは一人のヒヨコシッターが一匹ずつ順番にヒヨコを慰めます。一匹のヒヨコが泣き止むには1秒かかります。
// 泣いているヒヨコを1秒で慰める関数 func comfort(piyo string) string { time.Sleep(1 * time.Second) return strings.Replace(piyo, "(> e <)", "(・e・)ワーイ", 1) } func main() { start := time.Now() for i := 1; i <= 10; i++ { // 泣いてるヒヨコ piyo := fmt.Sprintf("[%v](> e <)", i) fmt.Println(piyo) // 慰める piyo = comfort(piyo) fmt.Println(piyo) } fmt.Printf("%f秒\n", (time.Now().Sub(start)).Seconds()) }出力結果
[1](> e <) [1](・e・)ワーイ [2](> e <) [2](・e・)ワーイ [3](> e <) [3](・e・)ワーイ ... 省略 ... [10](> e <) [10](・e・)ワーイ 10.030122秒すべてのヒヨコを慰め終わるまでに、約10秒かかります。
このプログラムを使って、並行処理の書き方を学んでいきます。1. goroutine で並行処理を実行する
goroutine は、Goのランタイムに管理される軽量なスレッドです。並行処理の肝です。
その実行は簡単で、go
の後に続けてスレッド化したい関数を呼び出すだけです。次のプログラムでは、泣いているヒヨコの数だけスレッドを作ってヒヨコシッターを増やすことができます。func main() { start := time.Now() for i := 1; i <= 10; i++ { // 泣いてるヒヨコ piyo := fmt.Sprintf("[%v](> e <)", i) // goroutine を作成 go func(piyo string) { fmt.Println(piyo) // 慰める piyo = comfort(piyo) fmt.Println(piyo) }(piyo) } // すべての処理が終わるまで待つ time.Sleep(2 * time.Second) fmt.Printf("%f秒\n", (time.Now().Sub(start)).Seconds()) }出力結果
[2](> e <) [7](> e <) [10](> e <) [1](> e <) [5](> e <) [3](> e <) [4](> e <) [8](> e <) [9](> e <) [6](> e <) [5](・e・)ワーイ [4](・e・)ワーイ [6](・e・)ワーイ [8](・e・)ワーイ [2](・e・)ワーイ [1](・e・)ワーイ [9](・e・)ワーイ [7](・e・)ワーイ [10](・e・)ワーイ [3](・e・)ワーイ 2.005158秒すべてのヒヨコが2秒で泣き止みました。同時に処理が実行されるのでヒヨコの順番はバラバラになります。
ちなみに、ねじおのMacのコア数は4です。func main() { fmt.Println(runtime.NumCPU()) } // 出力結果 // 42. WaitGroup で処理の終了を待つ
前のプログラムでは、goroutineの処理が終わるのを待つために最後に2秒のスリープを入れていました。しかし、実際には処理はもっと早く終わっているかもしれないし、もっと遅いかもしれません。もし、ヒヨコが泣き止む前に呼び出し元の処理が終了してしまうと、ヒヨコとヒヨコシッターは闇の世界に消えてしまいます。
そんな悲しい結末にならないように、sync.WaitGroupを使ってgoroutineの終了を確実に待機するようにします。
sync.WaitGroupの使い方は、4ステップです。
- WaitGroupをつくる。
wg := sync.WaitGroup{}
- goroutineを呼び出す前に、呼び出し元でインクリメントする。
wg.Add(1)
- goroutineの処理が終わったら、goroutine内でデクリメントする。
wg.Done()
- すべての処理が終わるのを待つ。
wg.Wait()
それでは、main()を書き換えます。
func main() { start := time.Now() wg := sync.WaitGroup{} //// 1. WaitGroupを作成 for i := 1; i <= 10; i++ { // 泣いてるヒヨコ piyo := fmt.Sprintf("[%v](> e <)", i) // goroutine を作成 wg.Add(1) //// 2. WaitGroupをインクリメント go func(piyo string) { defer wg.Done() //// 3. WaitGroupをデクリメント fmt.Println(piyo) // 慰める piyo = comfort(piyo) fmt.Println(piyo) }(piyo) } wg.Wait() //// 4. すべての処理が終わるまで待つ fmt.Printf("%f秒\n", (time.Now().Sub(start)).Seconds()) }出力結果
[10](> e <) [8](> e <) [9](> e <) [2](> e <) [4](> e <) [5](> e <) [6](> e <) [1](> e <) [7](> e <) [3](> e <) [7](・e・)ワーイ [3](・e・)ワーイ [10](・e・)ワーイ [8](・e・)ワーイ [9](・e・)ワーイ [2](・e・)ワーイ [6](・e・)ワーイ [4](・e・)ワーイ [5](・e・)ワーイ [1](・e・)ワーイ 1.000854秒これで無駄なく確実にgoroutineの終了を待てるようになりました。
3. channel でキューをつくる
さて、これまでのプログラムでは10匹のヒヨコに対して10人のヒヨコシッターがいました。実際にはそんな贅沢なことは稀です。ヒヨコシッターが2人しかいないときはどうすればよいでしょうか?
ここでは、泣いているヒヨコに一列に並んでもらい、何人かのヒヨコシッターが先頭のヒヨコから順番に慰めていくことにします。所謂、キューってやつですね。
Goにはchannelというスレッド間で通信する仕組みがあり、これを使うと簡単にキューを作ることができます。
キューを作成する
queue := make(chan データ型, キューのサイズ)キューに値を追加する
queue <- valキューに空き枠がない場合、この処理は空き枠ができるまでブロック(待機)します。
キューから値を取り出す
val, ok <-queue if !ok { // queue is closed. }キューが空で、かつ、キューの入り口が閉じていない場合、この処理はキューに値が入るまでブロック(待機)します。
キューが空で、かつ、キューの入り口が閉じている場合、ok
にfalse
が入ります。キューの入り口を閉じる
close(queue)キューの入り口が閉じれるとキューに値を追加できなくなります。
また、キューの受信者は、キューが空のとき、キューが閉じられていること(もう値が入ってくることはないこと)を知ることができます。では、main()を書き換えます。ヒヨコシッターは2人です。
const ( thread = 2 // スレッド数 ) func main() { start := time.Now() // キューを作成 queue := make(chan string, thread*2) // キューを処理するスレッドを作成 wg := sync.WaitGroup{} for i := 0; i < thread; i++ { // goroutine を作成 wg.Add(1) go func() { defer wg.Done() for { // キューからヒヨコを取り出す piyo, ok := <-queue if !ok { break } fmt.Println(piyo) // 慰める piyo = comfort(piyo) fmt.Println(piyo) } }() } // キューに泣いているヒヨコを追加 for i := 1; i <= 10; i++ { piyo := fmt.Sprintf("[%v](> e <)", i) queue <- piyo } close(queue) // キューを閉じる wg.Wait() // すべての処理が終わるまで待つ fmt.Printf("%f秒\n", (time.Now().Sub(start)).Seconds()) }出力結果
[2](> e <) [1](> e <) [1](・e・)ワーイ [3](> e <) [2](・e・)ワーイ [4](> e <) [3](・e・)ワーイ [5](> e <) [4](・e・)ワーイ [6](> e <) [5](・e・)ワーイ [7](> e <) [6](・e・)ワーイ [8](> e <) [7](・e・)ワーイ [9](> e <) [8](・e・)ワーイ [10](> e <) [10](・e・)ワーイ [9](・e・)ワーイ 5.008001秒うまくいったようです。2人で分担することで、約5秒で10匹のヒヨコを泣き止ますことができました。
さて、
退勤時間になったのでヒヨコシッターに仕事を中断させたいときにはどうすればよいのだろう?
ヒヨコが暴れだしてヒヨコシッターがパニックを起こしたらどうすれよいのだろう?
など疑問は残しつつも、今回はこれで終わります。OK。
ねじねじおでした。