Open35
ざっくりGoメモ
Goroutineを使わずにチャネルを使うときの挙動
謎
このコードがdeadlockになるのはなぜだろう?
(このコードの具体的な動作は実利的な意味がないところはさておき)
チャネル作成 → 送信 → 受信 はうまくいきそうな気がするんだけども。
package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<-ch)
}
code2: こっちは動く
「チャネル使うときはとりあえずGoroutine!」と思っていたけど、仕組みのところは分かってないのだなあ。
そもそもチャネルはなんのためにあるかというと、「Goroutine間のコミュニケーション」のため。
package main
import "fmt"
func main() {
ch := make(chan int)
go func() { ch <- 1 }()
fmt.Println(<-ch)
}
文字列の辞書順ソートの実装もっといい感じにできないかな?
案1
package main
import (
"fmt"
"sort"
"strings"
)
// MySortString 文字列を辞書順ソートしたコピーを返す
func MySortString(s string) string {
strs := make([]string, len(s))
for i, c := range s {
strs[i] = string(c)
}
sort.Strings(strs)
return strings.Join(strs, "")
}
func main() {
fmt.Println(MySortString("helloworld"))
fmt.Println(MySortString("edcba"))
fmt.Println(MySortString("abcde"))
}
案2
package main
import (
"fmt"
"sort"
"strings"
)
// MySortString 文字列を辞書順ソートしたコピーを返す
func MySortString(s string) string {
strs := make([]string, len(s))
for i, c := range s {
strs[i] = string(c)
}
sort.Strings(strs)
var out strings.Builder
out.Grow(len(s))
for _, c := range strs {
out.WriteString(c)
}
return out.String()
}
func main() {
fmt.Println(MySortString("helloworld"))
fmt.Println(MySortString("edcba"))
fmt.Println(MySortString("abcde"))
}
型エイリアスとDefinedTypeの区別
GoroutineLeak(ゴルーチンリーク)
詳しくはこっち↓↓
親のGoroutineが終了しても、子のGoroutineは終了しない
package main
import (
"fmt"
"net/http"
"runtime"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
ch := make(chan int)
go func() { ch <- 100 }()
go func() { ch <- 200 }()
fmt.Fprintf(w, "<-ch=%d, NumGoroutine=%d\n", <-ch, runtime.NumGoroutine())
})
http.ListenAndServe(":8080", nil)
}
sync.Once
のエッセンスを楽しめ!
こっちに詳しめに書いた↓↓
go vet
こぴーしちゃいけない系構造体 と ゼロ値でスタンバっている構造体の共通点ってなに?
sync/atomic
で遊ぶ。必要性を感じる
このへんに書いた
JSONあーだこーだ
-
json.Encoder
とjson.Decoder
json.RawMessage
- 構造体へのマッピング
- Marshal, UnMarshal
- embedを使う?
https://play.golang.org/p/prNRxi98iRv
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
const jsonStrBob = `{"name": "Bob", "age": 20}`
const jsonStrTom = `{"name": "Tom", "age": 35}`
var bob User
decoder1 := json.NewDecoder(strings.NewReader(jsonStrBob))
if err := decoder1.Decode(&bob); err != nil {
panic(err)
}
fmt.Printf("%+v\n", bob) // {Name:Bob Age:20}
fmt.Printf("%#v\n", bob) // main.User{Name:"Bob", Age:20}
var tom User
decoder2 := json.NewDecoder(strings.NewReader(jsonStrTom))
if err := decoder2.Decode(&tom); err != nil {
log.Fatalln(err)
}
fmt.Printf("%+v\n", tom) // {Name:Tom Age:35}
fmt.Printf("%#v\n", tom) // main.User{Name:"Tom", Age:35}
}
json.RawMessage
package main
import (
"bytes"
"encoding/json"
"fmt"
"strings"
)
// json.RawMessage でパースを遅延させる
func main() {
const jsonStr1 = `{"type": "foo", "data": {"hoge": 1}}`
const jsonStr2 = `{"type": "bar", "data": {"fuga": 2}}`
var v struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
decoder := json.NewDecoder(strings.NewReader(jsonStr1))
//decoder := json.NewDecoder(strings.NewReader(jsonStr2))
if err := decoder.Decode(&v); err != nil {
panic(err)
}
// json.RawMessage は []byte型
fmt.Printf("v.Data=%T\n", v.Data) // json.RawMessage
fmt.Printf("%+v\n", v) // {Type:foo Data:[123 34 104 111 103 101 34 58 32 49 125]}
switch v.Type {
case "foo":
var data struct {
Hoge int `json:"hoge"`
}
decoder := json.NewDecoder(bytes.NewReader(v.Data))
if err := decoder.Decode(&data); err != nil {
panic(err)
}
fmt.Printf("%+v\n", data)
case "bar":
var data struct {
Fuga int `json:"fuga"`
}
decoder := json.NewDecoder(bytes.NewReader(v.Data))
if err := decoder.Decode(&data); err != nil {
panic(err)
}
fmt.Printf("%+v\n", data)
}
}
json.Marshaler
と json.Unmarshaler
$ go doc json.Marshaler
package json // import "encoding/json"
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
Marshaler is the interface implemented by types that can marshal themselves
into valid JSON.
$ go doc json.Unmarshaler
package json // import "encoding/json"
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
Unmarshaler is the interface implemented by types that can unmarshal a JSON
description of themselves. The input can be assumed to be a valid encoding
of a JSON value. UnmarshalJSON must copy the JSON data if it wishes to
retain the data after returning.
By convention, to approximate the behavior of Unmarshal itself, Unmarshalers
implement UnmarshalJSON([]byte("null")) as a no-op.
signal.NotifyContext
signal.NotifyContext
でGraceful Shutdown
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
// Graceful Shutdown
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
server := &http.Server{Addr: ":8080"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(3 * time.Second) // 重たい処理のシミュレーション
fmt.Fprintln(w, "done")
})
// http.ListenAndServe() をゴルーチンで動かす
go func() {
fmt.Printf("server is running http://localhost%s\n", server.Addr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}()
<-ctx.Done()
if err := server.Shutdown(context.Background()); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
ゴルーチンで5つのリクエストを送る
- その後、サーバーに対してSIGINTを送ってもちゃんと
done
が返ってくる -
SIGINT
は、Ctrl+Cでおk
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"sync"
)
func main() {
var wg sync.WaitGroup
repeat := 5
wg.Add(repeat)
for i := 0; i < repeat; i++ {
go func(n int) {
defer wg.Done()
fmt.Printf("Request%d\n", n)
url := "http://localhost:8080"
resp, err := http.Get(url)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
}(i)
}
wg.Wait()
fmt.Println("Finish")
}
signal.NotifyContext
の例
package main
import (
"context"
"fmt"
"os"
"os/signal"
"time"
)
// https://pkg.go.dev/os/signal#example-NotifyContext
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// Ctrl+C で SIGINT を送信する → ctxがキャンセルされる
// なにもせず3秒経過する → <-time.After のcaseが採用される
select {
case <-time.After(3 * time.Second):
fmt.Println("シグナルは送信されませんでしたよ")
case <-ctx.Done():
fmt.Println()
fmt.Println(ctx.Err())
stop()
}
}
container/heap
で優先度付きキューを実現したい
- そもそも
container/heap
を扱うところから - heap sortとかもやりたい
- https://pkg.go.dev/container/heap
こっちに書いた!
AtCoderの問題をベースにしている
スライスと配列の関係
- スライスと配列ってどんな関係?
- 配列ってあんまり使わないからよくわからんね
- スライスのlenとcapの意味
- appendしたときの挙動
- appendしたときの挙動(メモリレベル)
- メモリの可視化
-
unsafe.Pointer
を使って、スライスが実際に持っているデータを覗く方法 - ポインタをずらす方法ないかな? ← なさそう
- スライスは
==
で直接比較できないのはなぜ? - スライスの状態の可視化ツールつくれたらいいなあ
『プログラミング言語Go』のp.94 4.2 スライス あたりを読むとよさそう
スライスは3つの構成要素を持つ
- Underlying Arrayのポインタ
- Length: 長さ
- Capacity: 容量
rutimer.slice.go
で定義されている
実際には$ go doc -u runtime.slice
package runtime // import "runtime"
type slice struct {
array unsafe.Pointer
len int
cap int
}
func growslice(et *_type, old slice, cap int) slice
スライスを構成する3つの要素
1. ポインタ
スライスを通して到達可能な配列の最初の要素を指しており、必ずしも配列の最初の要素とは限りません。
配列の先頭の要素かもしれないし
↓
| 3 | 1 | 4 | 1 | 5 | 9 | 2 |
↑
ここを指しているポインタかもしれない
2. 長さ == スライスの要素数
| 3 | 1 | 4 | 1 | 5 | 9 | 2 |
pointer: このパターンなら長さ(==スライスの要素数)は7
↓
| 3 | 1 | 4 | 1 | 5 | 9 | 2 |
| 3 | 1 | 4 | 1 | 5 | 9 | 2 |
↑
pointer: このパターンなら長さは3([]int{5, 9, 2}って感じ)
3. Capacity(容量)
長さが容量を超えることはありません。
容量はたいていスライスの開始から基底配列(Underlying Array)の終わりまでの要素数です。
実験: スライスの構成要素を確認する
package main
import "fmt"
func main() {
// months := [...]string のほうが楽な記述
months := [13]string{
"", // 添字と対応させるための番兵
"1月", "2月", "3月",
"4月", "5月", "6月",
"7月", "8月", "9月",
"10月", "11月", "12月",
}
summer := months[6:9]
q2 := months[4:7] // 第2四半期
// それぞれの構成要素に着目(ポインタも違うよね)
fmt.Printf("summer=%v, pointer=%p len=%d cap=%d\n", summer, summer, len(summer), cap(summer))
fmt.Printf(" q2=%v, pointer=%p len=%d cap=%d\n", q2,q2, len(q2), cap(q2))
}
$ go run main.go
summer=[6月 7月 8月], pointer=0xc000064200 len=3 cap=7
q2=[4月 5月 6月], pointer=0xc0000641e0 len=3 cap=9
図解
summer := months[6:9]
pointer
↓
| "" | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|-----------|
len=3
|---------------------------|
cap=7
q2 := months[4:7]
pointer
↓
| "" | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|-----------|
len=3
|--------------------------------------|
cap=9
実験: ポインタが同じかどうかを確認する
package main
import "fmt"
func main() {
months := [13]string{
"", // 添字と対応させるための番兵
"1月", "2月", "3月",
"4月", "5月", "6月",
"7月", "8月", "9月",
"10月", "11月", "12月",
}
// 開始地点が同じなら、同じポインタだよね
s1 := months[7:13]
s2 := months[7:10]
fmt.Printf("s1=%p\n", s1)
fmt.Printf("s2=%p\n", s2)
}
$ go run main.go
s1=0xc00009aca0
s2=0xc00009aca0
実験: 配列はメモリ上で連番で管理していることを確認する
package main
import "fmt"
func main() {
months := [13]string{
"", // 添字と対応させるための番兵
"1月", "2月", "3月",
"4月", "5月", "6月",
"7月", "8月", "9月",
"10月", "11月", "12月",
}
// 連番であることを確認する
for i := range months {
s := months[i:]
fmt.Printf("&months[%02d:]=%p\n", i, s)
}
}
$ go run main.go
&months[00:]=0xc00009ab60
&months[01:]=0xc00009ab70
&months[02:]=0xc00009ab80
&months[03:]=0xc00009ab90
&months[04:]=0xc00009aba0
&months[05:]=0xc00009abb0
&months[06:]=0xc00009abc0
&months[07:]=0xc00009abd0
&months[08:]=0xc00009abe0
&months[09:]=0xc00009abf0
&months[10:]=0xc00009ac00
&months[11:]=0xc00009ac10
&months[12:]=0xc00009ac20
未解決: 実験したい: Q. スライスのポインタをずらす実験。
- スライスのポインタをずらす(ex: 1つ先にすすめる)ことができれば、lenとcapはそれぞれ1ずつ減るよね?
-
unsafe
なやつを使えばいける?
実験: スライスはunderlying arrayの要素へのポインタを持っているんだから、スライスを通じて、underlying array を書き換えることができる
package main
import "fmt"
func main() {
underlyingArray := [...]int{3, 1, 4, 1, 5}
s := underlyingArray[:]
// スライスの要素を書き換える
for i := 0; i < len(s); i++ {
s[i] = 9
}
// underlying array も書き換わっている!(というか、underlying arrayを書き換えた)
fmt.Println(s) // [9 9 9 9 9]
fmt.Println(underlyingArray) // [9 9 9 9 9]
}
実験: スライスを通じて配列の要素順を逆にする
package main
import "fmt"
func main() {
underlyingArray := [...]int{3, 1, 4, 1, 5, 9, 2, 6, 5}
reverse := func(nums []int) {
// 両サイドから入れ替えていくアルゴリズム
for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
}
s := underlyingArray[:]
reverse(s)
// 当然、underlying arrayも書き換わっている
fmt.Println(s) // [5 6 2 9 5 1 4 1 3]
fmt.Println(underlyingArray) // [5 6 2 9 5 1 4 1 3]
}
==
で比較できないのなんでだろう?
疑問: スライスが 何を比較すればいいかもよくわからんけどね。
実験: スライスが3つの情報しかもってないことを確認する
package main
import (
"fmt"
"unsafe"
)
func main() {
a := [...]int64{3, 1, 4}
s := a[:]
fmt.Printf("len=%d cap=%d size=%d a=%v\n", len(a), cap(a), unsafe.Sizeof(a), a)
fmt.Printf("len=%d cap=%d size=%d s=%v\n", len(s), cap(s), unsafe.Sizeof(s), s)
// sliceは3つの情報(underlying arrayのポインタ, len, cap)しか持たないので、
// どれだけappendしようが、24Byte(==3 * 8Byte)で変わらない
s = append(s, 1)
s = append(s, 5)
s = append(s, 9)
s = append(s, 2)
fmt.Printf("len=%d cap=%d size=%d s=%v\n", len(s), cap(s), unsafe.Sizeof(s), s)
}
実験: appendでcapacityが足りない場合にアドレスが変わること、そして、新たなcapacityはどれくらいの大きさになるか
package main
import (
"fmt"
)
// appendの動き
// - capacityが足りないときは、およそ2倍のcapacityを確保する
// - capacityが増えるタイミングでポインタが変わる
func main() {
s := []int{0}
capacity := cap(s)
fmt.Printf("init: pointer=%p len=%d cap=%d\n", s, len(s), cap(s))
for i := 1; i <= (1<<20); i++ {
s = append(s, i)
if capacity < cap(s) {
capacity = cap(s)
fmt.Printf("i=%6d: pointer=%p len=%6d cap=%7d\n", i, s, len(s), cap(s))
}
}
}
$ go run main.go
init: pointer=0xc0000140c0 len=1 cap=1
i= 1: pointer=0xc0000140e0 len= 2 cap= 2
i= 2: pointer=0xc00001c080 len= 3 cap= 4
i= 4: pointer=0xc00001e0c0 len= 5 cap= 8
i= 8: pointer=0xc000072000 len= 9 cap= 16
i= 16: pointer=0xc000074000 len= 17 cap= 32
i= 32: pointer=0xc000076000 len= 33 cap= 64
i= 64: pointer=0xc000078000 len= 65 cap= 128
i= 128: pointer=0xc00007a000 len= 129 cap= 256
i= 256: pointer=0xc00007c000 len= 257 cap= 512
i= 512: pointer=0xc00007e000 len= 513 cap= 1024
i= 1024: pointer=0xc000100000 len= 1025 cap= 1280
i= 1280: pointer=0xc00010a000 len= 1281 cap= 1696
i= 1696: pointer=0xc000114000 len= 1697 cap= 2304
i= 2304: pointer=0xc000126000 len= 2305 cap= 3072
i= 3072: pointer=0xc00012c000 len= 3073 cap= 4096
i= 4096: pointer=0xc000134000 len= 4097 cap= 5120
i= 5120: pointer=0xc00013e000 len= 5121 cap= 7168
i= 7168: pointer=0xc00014c000 len= 7169 cap= 9216
i= 9216: pointer=0xc00015e000 len= 9217 cap= 12288
i= 12288: pointer=0xc000180000 len= 12289 cap= 15360
i= 15360: pointer=0xc00019e000 len= 15361 cap= 19456
i= 19456: pointer=0xc0001c4000 len= 19457 cap= 24576
i= 24576: pointer=0xc0001f4000 len= 24577 cap= 30720
i= 30720: pointer=0xc000230000 len= 30721 cap= 38912
i= 38912: pointer=0xc00027c000 len= 38913 cap= 49152
i= 49152: pointer=0xc0002dc000 len= 49153 cap= 61440
i= 61440: pointer=0xc000354000 len= 61441 cap= 76800
i= 76800: pointer=0xc0003ea000 len= 76801 cap= 96256
i= 96256: pointer=0xc000180000 len= 96257 cap= 120832
i=120832: pointer=0xc00026c000 len=120833 cap= 151552
i=151552: pointer=0xc000680000 len=151553 cap= 189440
i=189440: pointer=0xc000180000 len=189441 cap= 237568
i=237568: pointer=0xc0007f2000 len=237569 cap= 296960
i=296960: pointer=0xc000180000 len=296961 cap= 371712
i=371712: pointer=0xc000680000 len=371713 cap= 464896
i=464896: pointer=0xc000a0c000 len=464897 cap= 581632
i=581632: pointer=0xc000e7c000 len=581633 cap= 727040
i=727040: pointer=0xc000680000 len=727041 cap= 909312
i=909312: pointer=0xc000d70000 len=909313 cap=1136640
実験: スライスのメモリ上の配置のイメージをつかむ
package main
import (
"fmt"
)
// スライスのメモリ上の配置のイメージをつかむ
func main() {
// 8Byteずつずれる(int64で、len=1, capacity=1だから)
fmt.Printf("pointer=%p\n", make([]int, 1)) // 0xc0000ae010
fmt.Printf("pointer=%p\n", make([]int, 1)) // 0xc0000ae018
fmt.Printf("pointer=%p\n", make([]int, 1)) // 0xc0000ae020
// 16Byteずつずれる(int64で、len=2, capacity=2だから)
fmt.Printf("pointer=%p\n", make([]int, 2)) // 0xc0000ae030
fmt.Printf("pointer=%p\n", make([]int, 2)) // 0xc0000ae040
fmt.Printf("pointer=%p\n", make([]int, 2)) // 0xc0000ae050
// 4Byteずつずれる(int32で、len=1, capacity=1だから)
fmt.Printf("pointer=%p\n", make([]int32, 1)) // 0xc0000ae028
fmt.Printf("pointer=%p\n", make([]int32, 1)) // 0xc0000ae02c
fmt.Printf("pointer=%p\n", make([]int32, 1)) // 0xc0000ae030
// string: 16Byteずつずれる
fmt.Printf("pointer=%p\n", make([]string, 1)) // 0xc000096220
fmt.Printf("pointer=%p\n", make([]string, 1)) // 0xc000096230
fmt.Printf("pointer=%p\n", make([]string, 1)) // 0xc000096240
}
未解決: Q. make([]int, 0)を使うと初期のポインタは同じになるのはなぜ??
package main
import (
"fmt"
)
// メモリ上の配置のイメージをつかむ
// Q. make([]int, 0)を使うと初期のポインタは同じになるのはなぜ??
func main() {
fmt.Printf("pointer=%p\n", make([]int, 0)) // 0x118e370
fmt.Printf("pointer=%p\n", make([]int, 0)) // 0x118e370
fmt.Printf("pointer=%p\n", make([]int, 0)) // 0x118e370
}