Open35

ざっくりGoメモ

mohiramohira

Goroutineを使わずにチャネルを使うときの挙動

このコードがdeadlockになるのはなぜだろう?
(このコードの具体的な動作は実利的な意味がないところはさておき)

チャネル作成 → 送信 → 受信 はうまくいきそうな気がするんだけども。

https://play.golang.org/p/ZCgKs5ZQz1T

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)
}
mohiramohira

文字列の辞書順ソートの実装もっといい感じにできないかな?

案1

https://play.golang.org/p/xVal3SU7qRI

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

https://play.golang.org/p/8nnhSxM1wlM

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"))
}
mohiramohira

GoroutineLeak(ゴルーチンリーク)

詳しくはこっち↓↓
https://zenn.dev/mohira/scraps/95427e91e27c93#comment-9cc9196417379a

親の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)
}
mohiramohira

JSONあーだこーだ

  • json.Encoderjson.Decoder
  • json.RawMessage
  • 構造体へのマッピング
  • Marshal, UnMarshal
  • embedを使う?
mohiramohira

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}
}
mohiramohira

json.RawMessage

https://play.golang.org/p/V0muDXoHqG3

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)
	}
}
mohiramohira

json.Marshalerjson.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.
mohiramohira

signal.NotifyContext

mohiramohira

signal.NotifyContextでGraceful Shutdown

https://pkg.go.dev/os/signal#example-NotifyContext

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")
}
mohiramohira

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()
	}
}
mohiramohira

スライスと配列の関係

  • スライスと配列ってどんな関係?
  • 配列ってあんまり使わないからよくわからんね
  • スライスのlenとcapの意味
  • appendしたときの挙動
  • appendしたときの挙動(メモリレベル)
  • メモリの可視化
  • unsafe.Pointerを使って、スライスが実際に持っているデータを覗く方法
  • ポインタをずらす方法ないかな? ← なさそう
  • スライスは==で直接比較できないのはなぜ?
  • スライスの状態の可視化ツールつくれたらいいなあ

『プログラミング言語Go』のp.94 4.2 スライス あたりを読むとよさそう

mohiramohira

スライスは3つの構成要素を持つ

  1. Underlying Arrayのポインタ
  2. Length: 長さ
  3. 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
mohiramohira

スライスを構成する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)の終わりまでの要素数です。

mohiramohira

実験: スライスの構成要素を確認する

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
mohiramohira

実験: ポインタが同じかどうかを確認する

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
mohiramohira

実験: 配列はメモリ上で連番で管理していることを確認する

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
mohiramohira

未解決: 実験したい: Q. スライスのポインタをずらす実験。

  • スライスのポインタをずらす(ex: 1つ先にすすめる)ことができれば、lenとcapはそれぞれ1ずつ減るよね?
  • unsafeなやつを使えばいける?
mohiramohira

実験: スライスは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]
}
mohiramohira

実験: スライスを通じて配列の要素順を逆にする

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]
}
mohiramohira

疑問: スライスが == で比較できないのなんでだろう?

何を比較すればいいかもよくわからんけどね。

mohiramohira

実験: スライスが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)
}
mohiramohira

実験: 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
mohiramohira

実験: スライスのメモリ上の配置のイメージをつかむ

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
}
mohiramohira

未解決: 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
}
mohiramohira

ポインタってよくわかってないよね

  • ポインタ is 何?
  • ポインタ周りの演算子
  • *, &
  • ポインタのキャスト(?)
mohiramohira

違いを説明せよ

package main

import "fmt"

func main() {
    s := []int{10,20,30}

    fmt.Printf("%p\n", s)
    fmt.Printf("%p\n", &s)
}