被 Map 遍历打了脸

Map 遍历顺序是乱序的
var convertJavaTimeFormat = map[string][string] {
	"yyyy": "2006",
    "yy":   "06",
    "MM":   "01",
    "dd":   "02",
}

// ConvertTimeFormat formats t with java style format like yyyy-MM-dd.
func ConvertTimeFormat(t time.Time, javaStyleFmt string) string {
    s := javaStyleFmt
    for k, v := range convertJavaTimeFormat {
         s = strings.ReplaceAll(s, k, v)
    }
	return t.Format(s)
}
yyyyyyyyyyyyyyyy-MM-dd2021-06-082121-06-08

When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next. If you require a stable iteration order you must maintain a separate data structure that specifies that order.

示例,playground,代码拷贝自这里

package main

import "fmt"

func main() {
	blogArticleViews := map[string]int{
		"unix":         0,
		"python":       1,
		"go":           2,
		"javascript":   3,
		"testing":      4,
		"philosophy":   5,
		"startups":     6,
		"productivity": 7,
		"hn":           8,
		"reddit":       9,
		"C++":          10,
	}
	for key, views := range blogArticleViews {
		fmt.Println("There are", views, "views for", key)
	}
}

你猜想会出现下面这样吧:

There are 0 views for unix
There are 1 views for python
There are 2 views for go
There are 3 views for javascript
There are 4 views for testing
There are 5 views for philosophy
There are 6 views for startups
There are 7 views for productivity
There are 8 views for hn
There are 9 views for reddit
There are 10 views for C++

实际上呢,可能是这样的:

There are 3 views for javascript
There are 5 views for philosophy
There are 10 views for C++
There are 0 views for unix
There are 1 views for python
There are 2 views for go
There are 4 views for testing
There are 6 views for startups
There are 7 views for productivity
There are 8 views for hn
There are 9 views for reddit

改版如下:

var convertJavaTimeFormat = []string {
	"yyyy", "2006",
    "yy",   "06",
    "MM",   "01",
    "dd",   "02",
}

// ConvertTimeFormat formats t with java style format like yyyy-MM-dd.
func ConvertTimeFormat(t time.Time, javaStyleFmt string) string {
    s := javaStyleFmt
	k := convertJavaTimeFormat
    for i:=0; i+1 < len(k); i+=2 {
         s = strings.ReplaceAll(s, k[i], k[i+1])
    }
	return t.Format(s)
}

被切片切了一刀

package main

import (
   "fmt"
)

func main() {
   var a []byte
   a = append(a, []byte(`012345678`)...)
   fmt.Println(`len(a):`, len(a), `cap(a):`, cap(a))
   fmt.Printf("a: %s, a's addr %p\n", a, &a[0])
   b := append(a[:3], []byte(`...`)...)
   fmt.Println(`len(b):`, len(b), `cap(b):`, cap(b))
   fmt.Println(`len(a):`, len(a), `cap(a):`, cap(a))
   fmt.Printf("a: %s, a's addr %p, b: %s, b's addr %p\n", a, &a[0], b, &b[0])
}

输出结果

len(a): 9 cap(a): 16
a: 012345678, a's addr 0xc0000ae010
len(b): 6 cap(b): 16
len(a): 9 cap(a): 16
a: 012...678, a's addr 0xc0000ae010, b: 012..., b's addr 0xc0000ae010

被头等函数撞了一下头

package main

import (
	"fmt"
	"sync"
)

func cloneMap(src map[string]bool) map[string]bool {
	dest := make(map[string]bool)
	for k, v := range src {
		dest[k] = v
	}
	return dest
}

func NewHandler() func(string) {
	m := make(map[string]bool)
	return func(v string) {
		m = cloneMap(m)
		m[v] = true
	}
}

func main() {
	var wg sync.WaitGroup
	h := NewHandler()

	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()

			for j := 0; j < 10000; j++ {
				h(fmt.Sprintf("Index:%d", index))
			}
		}(i)
	}

	wg.Wait()
}

跑的结果是

fatal error: concurrent map iteration and map write
fatal error: concurrent map iteration and map write

也是不明所以,丈二和尚摸不着头脑,查了一会,才找到问题根源所在。

m := cloneMap(m)

for select default 空转忙

httpdump 说线上 CPU 都 100% 以上,本地跑一下,果然如此,赶紧上 pprof,一查一个准