# golang入门 **Repository Path**: zhangxl-gitee/golang-beginners-guide ## Basic Information - **Project Name**: golang入门 - **Description**: golang入门学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-11-23 - **Last Updated**: 2024-11-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[TOC](Go语言入门目录) --- # 第一章:基本程序结构 ## 1.变量、常量以及与其他语言的差异 **1.0 编写测试程序** 1.源码文件以_test结尾:xxx_test.go 2.测试方法名以Test开头:func TestXXX(t *testing.T){...} **1.1 变量赋值** 与其他主要编程语言的差异 - 赋值可以进行自动类型推断 - 在一个 赋值语句中可以对多个变量进行同时赋值 ```go func TestExchange(t *testing.T) { a := 1 b := 2 //tmp:=a //a=b //b=tmp a,b = b, a t.Log(a, b) } ``` **1.2 常量定义** 与其他主要编程语言的差异 快速设置连续值 ```go const ( Monday = iota+1 Tuesday Wednestday ) const ( Readable = 1 << iota Writable Exectable ) func TestConstantTry(t *testing.T) { t.Log(Monday, Tuesday) } func TestConstantTry1(t *testing.T) { a := 7 //0111 t.Log(a&Readable==Readable, a&Writable==Writable,a&Exectable==Exectable) } ``` --- ## 2.数据类型 **类型转化** 与其他主要编程语言的差异 1.Go语言不允许隐式类型转换 2.别名和原有类型也不能进行饮食类型转换 ```go func TestImplicit(t *testing.T) { var a int = 1 var b int64 b = a t.Log(a,b) } /* .\type_test.go:8:4: cannot use a (variable of type int) as int64 value in assignment */ ``` ```go type MyInt int64 func TestImplicit(t *testing.T) { var a int = 1 var b int64 b = int64(a) var c MyInt c = b t.Log(a, b, c) } /* .\type_test.go:12:6: cannot use b (variable of type int64) as MyInt value in assignment */ ``` **类型的预定义值** 1.math.MaxInt64 2.math.MaxFloat64 3.math.MaxUint32 **指针类型** 与其他主要编程语言的差异 1.不支持指针运算 2.string是值类型,其默认的初始化值为空字符串,而不是nil ```go func TestPoint(t *testing.T) { a := 1 aPtr := &a aPtr = aPtr + 1 t.Log(a, aPtr) t.Logf("%T %T", a, aPtr) } /* .\type_test.go:19:9: invalid operation: aPtr + 1 (mismatched types *int and untyped int) */ ``` ```go func TestString(t *testing.T) { var s string t.Log("*" + s + "*") //** t.Log(len(s)) //0 } ``` --- ## 3.运算符 **算数运算符** Go语言没有前置的++,-- **比较运算符** 用==比较数组 - 相同维数且含有相同个数元素的数组才可以比较 - 每个元素都相同的才相等 ```go func TestOperator(t *testing.T) { a := [...]int{1, 2, 3, 4} b := [...]int{1, 3, 4, 5} c := [...]int{1, 2, 3, 4, 5} d := [...]int{1, 2, 3, 4} t.Log(a == b) //false t.Log(a == c) //invalid operation: a == c (mismatched types [4]int and [5]int) t.Log(a == d) //true } ``` **逻辑运算符** **位运算符** 与其他主要编程语言的差异 &^ 按位清零 1 &^ 0 —— 1 1 &^ 1 —— 0 0 &^ 1 —— 0 0 &^ 0 —— 0 左右两个操作数,右操作数的位上为1,无论左操作数对应的位上是0还是1,都会被清零;如果右操作数的位上为0,左操作数对应的位值保持不变。 ```go const ( Readable = 1 << iota Writable Exectable ) func TestBitClear(t *testing.T) { a := 7 //0111 a = a &^ Readable a = a &^ Exectable t.Log(a&Readable == Readable, a&Writable == Writable, a&Exectable == Exectable) //false true false } ``` --- ## 4.条件和循环 **循环** 与其他主要编程语言的差异 Go语言仅支持循环关键字 for ```go //while条件循环 while(n<5) n := 0 for n<5{ n++ fmt.Println(n) } //无限循环 while(true) for{ } ``` **if条件** 与其他主要编程语言的差异 1.condition表达式结果必须为布尔值 2.支持变量赋值: ```go func TestIfMultiSec(t *testing.T) { if a := 1 == 1; a { t.Log("1==1") } } ``` **switch条件** 与其他主要编程语言的差异 1. 条件表达式不限制为常量或者整数; 2. 单个case中,可以出现多个结果选项,使用逗号分隔; 3. 与C语言等规则相反,Go语言不需要用break来明确退出一个case; 4. 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if...else...的逻辑作用等同 ```go func TestSwitchMultiCase(t *testing.T) { for i := 0; i < 5; i++ { switch i { case 0, 2: t.Log("Even") case 1, 3: t.Log("Odd") default: t.Log("It is not 0~3") } } } /* === RUN TestSwitchMultiCase condition_test.go:15: Even condition_test.go:17: Odd condition_test.go:15: Even condition_test.go:17: Odd condition_test.go:19: It is not 0~3 --- PASS: TestSwitchMultiCase (0.00s) PASS */ ``` ```go func TestSwitchCaseCondition(t *testing.T) { for i := 0; i < 5; i++ { switch { case i%2 == 0: t.Log("Even") case i%2 == 1: t.Log("Odd") default: t.Log("It is not 0~3") } } } ``` --- ## 5. 数组和切片 **数组的声明** var a [3]int //声明并初始化为默认零值 a[0]=1 b:=[3]int{1,2,3} //声明同时初始化 c:=[2][2]int{{1,2},{3,4}} //多维数组初始化 ```go func TestArrayInit(t *testing.T) { var arr [3]int arr1 := [4]int{1, 2, 3, 4} arr2 := [...]int{1, 3, 4, 5} arr1[1] = 5 t.Log(arr[1], arr[2]) t.Log(arr1, arr2) } ``` **数组元素对的遍历** ```go func TestArrayTravel(t *testing.T) { arr3 := [...]int{1, 2, 3, 4} for i := 0; i < len(arr3); i++ { t.Log(arr3[i]) } for _, e := range arr3 { t.Log(idx, e) } } ``` **数组截取** a[开始索引(包含),结束索引(不包含)] ```go func TestArraySection(t *testing.T) { arr := [...]int{1, 2, 3, 4, 5} arrSec := arr[:3] t.Log(arrSec) arrSec = arr[3:] t.Log(arrSec) arrSec = arr[:] t.Log(arrSec) } ``` **切片声明** ```go func TestSliceInit(t *testing.T) { var s0 []int t.Log(len(s0), cap(s0)) //0 0 s0 = append(s0, 1) t.Log(len(s0), cap(s0)) //1 1 s1 := []int{1, 2, 3, 4} t.Log(len(s1), cap(s1)) //4 4 s2 := make([]int, 3, 5) // []type, len, cap 其中len个元素会被初始为默认零值,未初始化元素不可访问 t.Log(len(s2), cap(s2)) //3 5 t.Log(s2[0], s2[1], s2[2]) //0 0 0 s2 = append(s2, 1) t.Log(s2[0], s2[1], s2[2], s2[3]) //0 0 0 1 t.Log(len(s2), cap(s2)) //4 5 } ``` **切片的自增长** ```go func TestSliceGrowing(t *testing.T) { s := []int{} for i := 0; i < 10; i++ { s = append(s, i) t.Log(len(s), cap(s)) } } /* slice_test.go:27: 1 1 slice_test.go:27: 2 2 slice_test.go:27: 3 4 slice_test.go:27: 4 4 slice_test.go:27: 5 8 slice_test.go:27: 6 8 slice_test.go:27: 7 8 slice_test.go:27: 8 8 slice_test.go:27: 9 16 slice_test.go:27: 10 16 */ ``` **切片的内存共享** ```go func TestSliceShareMemory(t *testing.T) { year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} Q2 := year[3:6] t.Log(Q2, len(Q2), cap(Q2)) //[Apr May Jun] 3 9 summer := year[5:8] t.Log(summer, len(summer), cap(summer)) //[Jun Jul Aug] 3 7 summer[0] = "Unknow" t.Log(Q2) //[Apr May Unknow] t.Log(year) //[Jan Feb Mar Apr May Unknow Jul Aug Sep Oct Nov Dec] } ``` **数组 vs. 切片** 1. 容量是否可伸缩:数组容量不可伸缩 2. 是否可以进行比较:相同维数相同长度的数组可比较,切片不可比较 ```go func TestSliceCompare(t *testing.T) { a := []int{1, 2, 3, 4} b := []int{1, 2, 3, 4} if a == b { //invalid operation: a == b (slice can only be compared to nil) t.Log("equal") } } ``` ## 6.Map声明、元素访问及遍历 **Map声明** ```go func TestInitMap(t *testing.T) { m1 := map[int]int{1: 1, 2: 4, 3: 9} t.Log(m1[2]) //4 t.Logf("len m1=%d", len(m1)) //len m1=3 m2 := map[int]int{} m2[4] = 16 t.Logf("len m2=%d", len(m2)) //len m2=1 m3 := make(map[int]int, 10) //初始化容量 t.Logf("len m3=%d", len(m3)) //len m3=0 } ``` **Map元素的访问** 与其他主要编程语言的差异 在访问的key不存在时,仍会返回零值,不能通过返回nil判断元素是否存在 ```go func TestAccessNotExistingKey(t *testing.T) { m1 := map[int]int{} t.Log(m1[1]) //0 m1[2] = 0 t.Log(m1[2]) //0 //主动判断一个是不存在还是其值本身就是0值 if v, ok := m1[3]; ok { t.Logf("Key 3's value is %d", v) } else { t.Log("Key 3 is not exit") //Key 3 is not exit } } ``` **Map的遍历** ```go func TestTravelMap(t *testing.T) { m1 := map[int]int{1: 1, 2: 4, 3: 9} for k, v := range m1 { t.Log(k, v) } } ``` ## 7.Map与工厂模式 - Map的value可以是一个方法 - 与Go的Dock type接口方式一起,可以方便的实现单一方法对象的工厂模式 ```go func TestMapWithFunValue(t *testing.T) { m := map[int]func (op int) int{} m[1] = func (op int) int { return op } m[2] = func (op int) int { return op * op } m[3] = func (op int) int { return op * op * op } t.Log(m[1](2), m[2](2), m[3](2)) //2 4 8 } ``` **实现Set** Go的内置集合中没有Set实现,可以map[type]bool 1.元素的唯一性 2.基本操作 1)添加元素 2)判断元素是否存在 3)删除元素 4)元素个数 ```go func TestMapForSet(t *testing.T) { mySet := map[int]bool{} mySet[1] = true n := 3 if mySet[n] { t.Logf("%d is existing", n) } else { t.Logf("%d is not existing", n) //3 is not existing } mySet[3] = true t.Log(len(mySet)) //2 delete(mySet, 1) n = 1 if mySet[n] { t.Logf("%d is existing", n) } else { t.Logf("%d is not existing", n) //1 is not existing } } ``` ## 8.字符串 与其他主要编程语言的差异 1. string是数据类型,不是引用或指针类型 2. string是只读的byte slice,len函数表示它所包含的byte数 3. string的byte数组可以存放任何数据 ```go func TestString(t *testing.T) { var s string t.Log(s) //初始化为默认零值,空字符 s = "hello" t.Log(len(s)) //5 //s[1]='3' //string是不可变的byte slice s = "\xE4\xB8\xA5" //可以存储任何二进制数据 t.Log(s) //严 t.Log(len(s)) //3 } ``` **Unicode UTF8** 1. Unicode是一种字符集 2. UTF8是unicode的存储实现(转换为字节序列的规则) ```go func TestString(t *testing.T) { var s string s = "中" t.Log(len(s)) //3 是byte的长度 c := []rune(s) t.Log(len(c)) //1 是unicode编码字符的长度 t.Logf("中 unicode %x", c[0]) //中 unicode 4e2d t.Logf("中 UTF8 %x", s) //中 UTF8 e4b8ad } ``` **编码与存储** |字符|"中" | |--|--| | Unicode |0x4E2D | | UTF-8|0xE4B8AD | | string/[]byte|[0xE4,0XB8,0XAD] | **常用字符串函数** 1. strings包 2. strconv包 ```go func TestStringFn(t *testing.T) { s := "A,B,C" parts := strings.Split(s, ",") for _, part := range parts { t.Log(part) } t.Log(strings.Join(parts, "-")) //A-B-C } func TestConv(t *testing.T) { s := strconv.Itoa(10) t.Log("str" + s) //str10 if i, err := strconv.Atoi("10"); err == nil { t.Log(10 + i) //20 } } ``` ## 9.Go语言的函数 **函数是一等公民** 与其他主要编程语言的差异 1. 可以有多个返回值 ```go func returnMultiValues() (int, int) { return rand.Intn(10), rand.Intn(20) } func TestFn(t *testing.T) { a, _ := returnMultiValues() t.Log(a) } ``` **函数式编程** ```go func TestFn(t *testing.T) { a, _ := returnMultiValues() t.Log(a) tsSF := timeSpent(slowFun) t.Log(tsSF(10)) } func timeSpent(inner func (op int) int) func (op int) int { return func (op int) int { start := time.Now() ret := inner(op) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFun(op int) int { time.Sleep(time.Second) return op } /* func_test.go:16: 1 time spent: 1.0023888 func_test.go:18: 10 --- PASS: TestFn (1.00s) */ ``` 2. 所有参数都是值传递:slice,map,channel会有传引用的错觉 3. 函数可以作为变量的值 4. 函数可以作为参数和返回值 **可变长参数和defer** ```go func sum(ops ...int) int { ret := 0 for _, op := range ops { ret += op } return ret } func TestVarParam(t *testing.T) { t.Log(sum(1, 2, 3, 4, 5)) //15 } ``` ```go func TestDefer(t *testing.T) { defer func () { t.Log("Clear resource") }() t.Log("Started") panic("Fatal error") //defer仍会执行 } ``` ## 10.行为的定义和实现 **结构体定义** **实例创建及初始化** ```go func TestCreateEmployeeObj(t *testing.T) { e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) //返回指针 e2.Id = "2" e2.Age = 22 e2.Name = "Rose" t.Log(e) t.Log(e1) t.Log(e1.Id) t.Log(e2) t.Logf("e is %T", e) t.Logf("e2 is %T", e2) } /* func_test.go:67: {0 Bob 20} func_test.go:68: { Mike 30} func_test.go:69: func_test.go:70: &{2 Rose 22} func_test.go:71: e is fn_test.Employee func_test.go:72: e2 is *fn_test.Employee */ ``` **行为(方法)定义** 与其他主要编程语言的差异 ```go //第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制 func (e Employee) String() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c520 return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } func TestStructOperation(t *testing.T) { e := Employee{"0", "Bob", 20} fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c4f0 t.Log(e.String()) //ID:0-Name:Bob-Age:20 } ``` ```go //通常情况下为了避免内存拷贝我们使用第二种定义方式 func (e *Employee) String() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c00001c4f0 return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } func TestStructOperation(t *testing.T) { e := Employee{"0", "Bob", 20} fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) //Address is c0000884c0 t.Log(e.String()) //ID:0-Name:Bob-Age:20 } ``` ## 11.Go语言的相关接口 **接口与依赖** 与其他主要编程语言的差异 1. 接口为非入侵性,实现不依赖于接口定义 2. 所以接口的定义可以包含在接口使用者包内 ```go type Programmer interface { WriteHelloWorld() string } type GoProgrammer struct { } func (g *GoProgrammer) WriteHelloWorld() string { return "fmt.Println(\"Hello World\")" } func TestClient(t *testing.T) { var p Programmer //定义接口变量 p = new(GoProgrammer) //实例化类型 t.Log(p.WriteHelloWorld()) //fmt.Println("Hello World") } ``` **接口变量** ```go //Programmer是一个接口,GoProgrammer是它的一个实现,prog是类型是接口的变量 var prog Programmer = &GoProgrammer{} //当prog被初始化之后有两个部分: //1.类型:实现这个接口的类型 type GoProgrammer struct { } //2.数据:真正实现这个类型GoProgrammer的一个实例 &GoProgrammer{} ``` **自定义类型** 1. type IntConv func(n int)int 2. type MyPoint int ```go type IntConv func (op int) int func timeSpent(inner IntConv) IntConv { return func (op int) int { start := time.Now() ret := inner(op) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFun(op int) int { time.Sleep(time.Second) return op } func TestCustomerType(t *testing.T) { tsSF := timeSpent(slowFun) t.Log(tsSF(10)) } ``` ## 12.扩展与复用 ```go type Pet struct { } func (p *Pet) Speak() { fmt.Printf("...") } func (p *Pet) SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { p *Pet } func (d *Dog) Speak() { //d.p.Speak() fmt.Println("Wang!") //方法重载 } func (d *Dog) SpeakTo(host string) { //d.p.SpeakTo(host) d.Speak() //方法重载 fmt.Println(" ", host) //方法重载 } func TestDog(t *testing.T) { dog := new(Dog) dog.SpeakTo("hao") //... hao //重载结果: //Wang! // hao } ``` ```go type Pet struct { } func (p *Pet) Speak() { fmt.Printf("...") } func (p *Pet) SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { Pet } func TestDog(t *testing.T) { dog := new(Dog) dog.SpeakTo("hao") //... hao } ``` 内嵌的结构类型完全没法当成继承来用,它不支持访问子类的方法、数据,不支持重载,不支持LSP ```go type Pet struct { } func (p *Pet) Speak() { fmt.Printf("...") } func (p *Pet) SpeakTo(host string) { p.Speak() fmt.Println(" ", host) } type Dog struct { Pet } func (d *Dog) Speak() { fmt.Print("Wang!") } func TestDog(t *testing.T) { var dog Pet = new(Dog) //cannot use new(Dog) (value of type *Dog) as Pet value in variable declaration dog.SpeakTo("hao") } ``` ## 13.不一样对的接口类型,一样 的多态 ```go type Code string type Programmer interface { WriteHelloWorld() Code } type GoProgrammer struct { } func (g *GoProgrammer) WriteHelloWorld() Code { return "fmt.Println(\"Hello World!\")" } type JavaProgrammer struct { } func (p *JavaProgrammer) WriteHelloWorld() Code { return "System.out.Println(\"Hello World!\")" } func writeFirstProgram(p Programmer) { fmt.Printf("%T %v\n", p, p.WriteHelloWorld()) } func TestPolymorphism(t *testing.T) { //goProg := new(GoProgrammer) goProg := &GoProgrammer{} javaProg := new(JavaProgrammer) writeFirstProgram(goProg) //*polymorphism.GoProgrammer fmt.Println("Hello World!") writeFirstProgram(javaProg) //*polymorphism.JavaProgrammer System.out.Println("Hello World!") } ``` **空接口与断言** 1. 空接口可以表示任何类型 2. 通过断言来将空接口转换为制定类型 v,ok:=p.(int) //ok=true 时为转换成功 ```go func DoSomething(p interface{}) { switch v := p.(type) { case int: fmt.Println("Integer ", v) case string: fmt.Println("String ", v) default: fmt.Println("Unkown Type") } //if i, ok := p.(int); ok { // fmt.Println("Integer ", i) // return //} //if i, ok := p.(string); ok { // fmt.Println("String ", i) // return //} //fmt.Println("Unkown Type") } func TestEmptyInterfaceAssertion(t *testing.T) { DoSomething(10) //Integer 10 DoSomething("10") //String 10 } ``` **Go接口最佳实践** 1. 倾向于使用小的接口定义,很多接口只包含一个方法 ```go type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } ``` 2. 较大的接口定义,可以由多个小接口定义组合而成 ```go type ReadWriter interface { Reader Writer } ``` 3. 只依赖于必要功能的最小接口 ```go func StoreData(reader Reader) error { ... } ``` ## 13.编写好的错误处理 **Go的错误机制** 与其他主要编程语言的差异 1. 没有异常机制 2. error类型实现了error接口 ```go type error interface { Error() string } ``` 3. 可以通过errors.New来快速创建错误实例 ```go errors.New("n must be in the range [0,100]") ``` **panic** - panic用于不可恢复的错误 - panic退出前会执行defer指定的内容 **panic vs. os.Exit** - os.Exit退出时不会调用defer指定的函数 - os.Exit退出时不输出当前调用栈信息 ```go func TestPanicVsExit(t *testing.T) { defer func () { fmt.Println("Finally!") }() fmt.Println("Start") panic(errors.New("Something wrong!")) /* Start Finally! --- FAIL: TestPanicVsExit (0.00s) panic: Something wrong! [recovered] panic: Something wrong! */ } ``` ```go func TestPanicVsExit(t *testing.T) { defer func () { fmt.Println("Finally!") }() fmt.Println("Start") os.Exit(-1) /* Start */ } ``` **recover** ```go //Java try{ ... }catch(Throwable t){ } //C++ try{ ... }catch(...){ } ``` ```go defer func () { if err := recover(); err != nil { //恢复错误 } }() ``` **当心!recover成为恶魔** - 形成僵尸服务进程,导致health check失效 - "Let it Crash!"往往是我们恢复不确定性错误的最好方法 ```go func TestPanicVsExit(t *testing.T) { defer func () { if err := recover(); err != nil { fmt.Println("recovered from ", err) } }() fmt.Println("Start") panic(errors.New("Something wrong!")) /* Start recovered from Something wrong! --- PASS: TestPanicVsExit (0.00s) PASS */ } ``` ## 14.构建可复用的模块(包) **package** 1. 基本复用模块单元(以首字母大写来表明可被包外代码访问) 2. 代码的package可以和所在的目录不一致 3. 同一目录里的Go代码的package要保持一致 ```go //src/ch12/series/my_series.go package series func GetFib(n int) []int { ret := []int{1, 1} for i := 2; i < n; i++ { ret = append(ret, ret[i-1]+ret[i-2]) } return ret } ``` ```go //src/ch12/client/package_test.go package client import ( "goprojects/golang-beginners-guide/src/ch12/series" "testing" ) func TestPackage(t *testing.T) { t.Log(series.GetFib(5)) } ``` **init方法** - 在main被执行前,所有依赖的package的init方法都会被执行 - 不同包的init函数按照包导入的依赖关系决定执行顺序 - 每个包可以有多个init函数 - 包的每个源文件也可以有多个init函数,这点比较特殊 ```go package series import "fmt" func GetFib(n int) []int { ret := []int{1, 1} for i := 2; i < n; i++ { ret = append(ret, ret[i-1]+ret[i-2]) } return ret } func Square(n int) int { return n * n } func init() { fmt.Println("init1") } func init() { fmt.Println("init2") } ``` ```go package client import ( "goprojects/golang-beginners-guide/src/ch12/series" "testing" ) func TestPackage(t *testing.T) { t.Log(series.GetFib(5)) t.Log(series.Square(2)) } /* init1 init2 === RUN TestPackage package_test.go:9: [1 1 2 3 5] package_test.go:10: 4 --- PASS: TestPackage (0.00s) PASS */ ``` **package下载** - 通过go get 来获取远程依赖(go get -u 强制从网络更新远程依赖) - 注意代码在Github上的组织形式,以适应go get(直接以代码路径开始,不要有src) **常用的依赖管理工具** - godep - glide - dep ## 15.协程机制 **Thead vs. Groutine** 1. 创建时默认的stack的大小 - JDK5以后Java Thread stack默认为1M - Groutine的Stack初始化大小为2K 2. 和KSE(Kernel Space Entity)的对应关系 - Java Thread是1:1 - Groutine是M:N ```go func TestGroutine(t *testing.T) { for i := 0; i < 10; i++ { go func (i int) { fmt.Printf("%d ", i) //0 6 7 8 5 2 1 3 9 4 }(i) } time.Sleep(time.Millisecond*50) } ``` go的方法调用传递的都是值传递,i被复制了一份,每个协程中所用的i的地址是不一样的,没有竞争关系,是可以正确执行的。 **错误写法** ```go func TestErrGroutine(t *testing.T){ for i := 0; i < 10; i++ { go func () { fmt.Printf("%d ", i) }() } time.Sleep(time.Millisecond*50) //10 10 10 10 10 10 10 10 10 10 } ``` 因为i这个 变量在test所在的协程以及for遍历中启动的其他的协程中被共享了,存在竞争条件。需要锁的机制来完成。 ## 16.共享内存并发机制 ```go func TestCounter(t *testing.T) { counter := 0 for i := 0; i < 5000; i++ { go func () { counter++ }() } time.Sleep(1*time.Second) t.Logf("counter=%d", counter) //counter=4978 } ``` 因为counter在不同协程中做自增,导致并发的竞争条件,不是线程安全的程序。需要对共享内存做锁的保护。 ```go func TestCounter(t *testing.T) { var mut sync.Mutex counter := 0 for i := 0; i < 5000; i++ { go func () { defer func () { mut.Unlock() }() mut.Lock() counter++ }() } time.Sleep(1*time.Second) t.Logf("counter=%d", counter) //counter=5000 } ``` **WaitGroup** WaitGroup同步各个线程之间的方法,只有当所有等待的任务都完成之后才能继续往下执行。 ```go func TestCounter(t *testing.T) { var mut sync.Mutex counter := 0 for i := 0; i < 5000; i++ { go func () { defer func () { mut.Unlock() }() mut.Lock() counter++ }() } //time.Sleep(1*time.Second) t.Logf("counter=%d", counter) //counter=4990 } ``` 睡眠时间注释掉后,外面test协程执行的速度超过了for中所有协程执行完的速度。导致for中协程还没执行完就退掉了。 ```go func TestCounter(t *testing.T) { var mut sync.Mutex var wg sync.WaitGroup counter := 0 for i := 0; i < 5000; i++ { wg.Add(1) go func () { defer func () { mut.Unlock() }() mut.Lock() counter++ wg.Done() }() } wg.Wait() t.Logf("counter=%d", counter) //counter=5000 } ``` ## 17.CSP并发机制 **CSP vs. Actor** ![Actor Model](https://i-blog.csdnimg.cn/direct/37aeb38a36204396a97db1cc64fa8e8e.png) - 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些。 - Go中channel是有容量限制并且独立于处理Groutine,而如Erlang,Actor模式中的messagebox容量是无限。 - Golang协程会主动从channel中去处理channel传过来的消息,Actor模式进程总是被动地处理消息。 **channel机制** ![channel两种机制](https://i-blog.csdnimg.cn/direct/f50d8d089ea341ba9830e3f58a0dc382.png) 串行任务处理 ```go func service() string { time.Sleep(time.Millisecond * 50) return "Done" } func otherTask() { fmt.Println("working on something else") time.Sleep(time.Millisecond * 100) fmt.Println("Task is done") } func TestService(t *testing.T) { fmt.Println(service()) otherTask() } /* Done working on something else Task is done --- PASS: TestService (0.17s) */ ``` 改造为异步处理任务(无缓冲通道) ```go func AsyncService() chan string { retCh := make(chan string) go func() { ret := service() fmt.Println("return result.") retCh <- ret fmt.Println("service exited.") }() return retCh } func TestAsynService(t *testing.T) { retCh := AsyncService() otherTask() fmt.Println(<-retCh) } /* working on something else return result. Task is done Done service exited. --- PASS: TestAsynService (0.11s) */ ``` 更高效的异步处理任务(有缓冲通道) ```go func AsyncService() chan string { retCh := make(chan string, 1) go func () { ret := service() fmt.Println("return result.") retCh <- ret fmt.Println("service exited.") }() return retCh } func TestAsynService(t *testing.T) { retCh := AsyncService() otherTask() fmt.Println(<-retCh) } /* working on something else return result. service exited. Task is done Done --- PASS: TestAsynService (0.10s) */ ``` ## 18.多路选择和超时控制 **多渠道选择** **超时控制** ```go func TestSelect(t *testing.T) { select { case ret := <-AsyncService(): t.Logf(ret) case <-time.After(time.Millisecond * 10): t.Error("time out") } } ``` ## 19.channel的关闭和广播 **channel的关闭** - 向关闭的channel发送数据,会导致panic - v,ok<-ch;ok为bool,true表示正常接受,false表示通道关闭 - 所有的channel接收者都会在channel关闭时,立即从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者发送信号。如:退出信号 ```go func dataProducer(ch chan int, wg *sync.WaitGroup) { go func () { for i := 0; i < 10; i++ { ch <- i } close(ch) wg.Done() }() } func dataReceiver(ch chan int, wg *sync.WaitGroup) { go func () { for { if data, ok := <-ch; ok { fmt.Println(data) } else { break } } wg.Done() }() } func TestCloseChannel(t *testing.T) { var wg sync.WaitGroup ch := make(chan int) wg.Add(1) dataProducer(ch, &wg) wg.Add(1) dataReceiver(ch, &wg) wg.Add(1) dataReceiver(ch, &wg) wg.Wait() } ``` **任务的取消** ```go func isCancelled(cancelChan chan struct{}) bool { select { case <-cancelChan: return true default: return false } } func cancel_1(cancelChan chan struct{}) { cancelChan <- struct{}{} } func cancel_2(cancelChan chan struct{}) { close(cancelChan) } func TestCancel(t *testing.T) { cancelChan := make(chan struct{}, 0) for i := 0; i < 5; i++ { go func (i int, cancelCh chan struct{}) { for { if isCancelled(cancelChan) { break } time.Sleep(time.Millisecond * 5) } fmt.Println(i, "Cancelled") }(i, cancelChan) } //cancel_1(cancelChan) //4 Cancelled 只取消掉一个任务 cancel_2(cancelChan) //广播处理,取消掉所有任务 /* 4 Cancelled 2 Cancelled 3 Cancelled 1 Cancelled 0 Cancelled */ time.Sleep(time.Second * 1) } ``` ## 20.Context与任务取消 **关联任务的取消** ![关联任务的取消](https://i-blog.csdnimg.cn/direct/ff39caf39e7541a7a52b72f066ccdfa4.png) **Context** - 根Context:通过context.Background()创建 - 子Context:context.WithCancel(parentContext)创建 - ctx,cancel:=context.WithCancel(context.Background()) - 当前Context被取消时,基于它的子context都会被取消 - 接收取消通知<-ctx.Done() **使用context实现channel的任务取消** ```go func isCancelled(ctx context.Context) bool { select { case <-ctx.Done(): return true default: return false } } func TestCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 5; i++ { go func (i int, ctx context.Context) { for { if isCancelled(ctx) { break } time.Sleep(time.Millisecond * 5) } fmt.Println(i, "Cancelled") }(i, ctx) } cancel() time.Sleep(time.Second * 1) } /* 4 Cancelled 2 Cancelled 3 Cancelled 0 Cancelled 1 Cancelled */ ``` ## 21.典型并发任务 **只运行一次** **单例模式(懒汉式,线程安全)** ![懒汉式线程安全单例模式](https://i-blog.csdnimg.cn/direct/88dbbb1d5dae4edbbb8793f502b6b03c.png) Go语言实现单例模式(懒汉式,线程 安全) ```go var once sync.Once var obj *SingletonObj func GetSingletonObj() *SingletonObj { once.Do(func () { fmt.Println("Create Singleton obj.") obj = &SingletonObj{} }) return obj } ``` 范例如下: ```go type Singleton struct { } var singleInstance *Singleton var once sync.Once func GetSingletonObj() *Singleton { once.Do(func () { fmt.Println("Create obj") singleInstance = new(Singleton) }) return singleInstance } func TestGetSingletonObj(t *testing.T) { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func () { obj := GetSingletonObj() fmt.Printf("%x\n", unsafe.Pointer(obj)) wg.Done() }() } wg.Wait() } /* Create obj beba40 beba40 beba40 beba40 beba40 beba40 beba40 beba40 beba40 beba40 */ ``` **仅需任意任务完成** ```go func runTask(id int) string { time.Sleep(10 * time.Millisecond) return fmt.Sprintf("The result is from %d", id) } func FirstResponse() string { numOfRunner := 10 //ch := make(chan string) //无缓冲通道,没人接收消息会阻塞,直到有人接收消息为止,这会导致协程泄露 /* first_response_test.go:28: Before: 2 first_response_test.go:29: The result is from 6 first_response_test.go:31: After: 11 */ ch := make(chan string, numOfRunner) /* first_response_test.go:34: Before: 2 first_response_test.go:35: The result is from 1 first_response_test.go:37: After: 2 */ for i := 0; i < numOfRunner; i++ { go func (i int) { ret := runTask(i) ch <- ret }(i) } return <-ch } func TestFirstResponse(t *testing.T) { t.Log("Before:", runtime.NumGoroutine()) //输出当前系统中的协程数 t.Log(FirstResponse()) time.Sleep(time.Second * 1) t.Log("After:", runtime.NumGoroutine()) } ``` **CSP机制实现所有任务完成** ```go func runTask(id int) string { time.Sleep(10 * time.Millisecond) return fmt.Sprintf("The result is from %d", id) } func AllResponse() string { numOfRunner := 10 ch := make(chan string, numOfRunner) for i := 0; i < numOfRunner; i++ { go func (i int) { ret := runTask(i) ch <- ret }(i) } finalRet := "" for j := 0; j < numOfRunner; j++ { finalRet += <-ch + "\n" } return finalRet } func TestFirstResponse(t *testing.T) { t.Log("Before:", runtime.NumGoroutine()) //输出当前系统中的协程数 t.Log(AllResponse()) time.Sleep(time.Second * 1) t.Log("After:", runtime.NumGoroutine()) } /* util_all_done_test.go:32: Before: 2 util_all_done_test.go:33: The result is from 3 The result is from 2 The result is from 1 The result is from 9 The result is from 8 The result is from 5 The result is from 6 The result is from 0 The result is from 4 The result is from 7 util_all_done_test.go:35: After: 2 */ ``` **对象池** 使用buffered channel实现对象池 ```go type ReusableObj struct { } type ObjPool struct { bufChan chan *ReusableObj //用于缓冲可重用对象 } func NewObjPool(numOfObj int) *ObjPool { objPool := ObjPool{} objPool.bufChan = make(chan *ReusableObj, numOfObj) for i := 0; i < numOfObj; i++ { objPool.bufChan <- &ReusableObj{} } return &objPool } func (p *ObjPool) GetObj(timeout time.Duration) (*ReusableObj, error) { select { case ret := <-p.bufChan: return ret, nil case <-time.After(timeout): return nil, errors.New("time out") } } func (p *ObjPool) ReleaseObj(obj *ReusableObj) error { select { case p.bufChan <- obj: return nil default: return errors.New("overflow") } } func TestObjPool(t *testing.T) { pool := NewObjPool(10) for i := 0; i < 11; i++ { if v, err := pool.GetObj(time.Second * 1); err != nil { t.Error(err) } else { fmt.Printf("%T\n", v) /* *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj *obj_pool.ReusableObj obj_pool_test.go:13: time out Done */ if err := pool.ReleaseObj(v); err != nil { t.Error(err) } } } fmt.Println("Done") } ``` **sync.Pool对象缓存** **sync.Pool对象的获取** ![sync.pool对象获取](https://i-blog.csdnimg.cn/direct/d94960d37beb44369122d4bcffc504e6.png) - 尝试从私有对象获取 - 私有对象不存在,尝试从当前Processor的共享池获取 - 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取 - 如果所有子池都是空的,最后就用用户指定 的New函数产生一个新的对象返回 **sync.Pool对象的放回** - 如果私有对象不存在则保存为私有对象 - 如果私有对象存在,放入当前Processor子池的共享池中 **使用sync.Pool** ```go pool := &sync.Pool{ New: func () interface{} { return 0 }, } arry := pool.Get().(int) ... pool.Put(10) ``` 应用举例 ```go func TestSyncPool(t *testing.T) { pool := &sync.Pool{New: func () interface{} { fmt.Println("Create a new object.") return 100 }} v := pool.Get().(int) fmt.Println(v) pool.Put(3) v1, _ := pool.Get().(int) fmt.Println(v1) } /* Create a new object. 100 3 */ ``` **sync.Pool对象的声明周期** - GC会清除sync.Pool缓存的对象 - 对象的缓存有效期为下一次GC之前 ```go func TestSyncPool(t *testing.T) { pool := &sync.Pool{New: func () interface{} { fmt.Println("Create a new object.") return 100 }} v := pool.Get().(int) fmt.Println(v) pool.Put(3) runtime.GC() //GC会清除sync.Pool中缓存的对象 v1, _ := pool.Get().(int) fmt.Println(v1) } /* Create a new object. 100 Create a new object. 100 */ ``` 多协程下池对的对象缓存机制 ```go func TestSyncPoolInMultiGroutine(t *testing.T) { pool := &sync.Pool{New: func () interface{} { fmt.Println("Create a new object.") return 10 }} pool.Put(100) pool.Put(100) pool.Put(100) var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func (id int) { fmt.Println(pool.Get()) wg.Done() }(i) } wg.Wait() } /* 100 100 100 Create a new object. 10 Create a new object. Create a new object. 10 Create a new object. 10 10 Create a new object. 10 Create a new object. 10 Create a new object. 10 */ ``` **sync.Poo.总结** - 适用于通过复用,降低复杂对象的创建和GC代价 - 协程安全,会有锁的开销 - 生命周期受GC影响,不适合于做连接池等,需要自己管理生命周期的资源的池化 ## 22.测试 **内置单元测试框架** - Fail,Error:该测试失败,该测试继续,其他测试继续执行 - FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行 - 代码覆盖率(go test -v -cover) - 断言 **Benchmark** ```go func BenchmarkConcatStringByAdd(b *testing.B) { //与性能测试无关的代码 b.ResetTimer() for i := 0; i < b.N; i++ { //测试代码 } b.StopTimer() //与性能测试无关的代码 } ``` ```go func TestConcatStringByAdd(t *testing.T) { assertion := assert.New(t) elems := []string{"1", "2", "3", "4", "5"} ret := "" for _, elem := range elems { ret += elem } assertion.Equal("12345", ret) } func TestConcatStringByBytesBuffer(t *testing.T) { assertion := assert.New(t) var buf bytes.Buffer elems := []string{"1", "2", "3", "4", "5"} for _, elem := range elems { buf.WriteString(elem) } assertion.Equal("12345", buf.String()) } func BenchmarkConcatStringByAdd(b *testing.B) { //与性能测试无关的代码 elems := []string{"1", "2", "3", "4", "5"} b.ResetTimer() for i := 0; i < b.N; i++ { //测试代码 ret := "" for _, elem := range elems { ret += elem } } b.StopTimer() //与性能测试无关的代码 } /* goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch15/benchmark cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkConcatStringByAdd BenchmarkConcatStringByAdd-16 10967497 110.8 ns/op PASS */ func BenchmarkConcatStringByBytesBuffer(b *testing.B) { elems := []string{"1", "2", "3", "4", "5"} b.ResetTimer() for i := 0; i < b.N; i++ { var buf bytes.Buffer for _, elem := range elems { buf.WriteString(elem) } } b.StopTimer() } /* goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch15/benchmark cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkConcatStringByBytesBuffer BenchmarkConcatStringByBytesBuffer-16 18415782 68.19 ns/op PASS */ ``` **go test 的使用补充** [参考链接](https://blog.csdn.net/DisMisPres/article/details/118610983) ### 基础用法 源文件和测试文件放在同一目录下,测试文件以 _test 结尾,这个是固定格式,使用 go build 进行编译时,_test 文件不会编译。 1. 每个测试函数需要以 Test 为前缀,例如:Test_Division, TestDivision 2. 每个性能测试函数需要以 Benchmark 为前缀,例如:Benchmark_Division, BenchmarkDivision ### go test 参数 **打印测试函数的所有细节 -v** ```go go test -v ``` ```go benchmark> go test -v == = RUN TestConcatStringByAdd --- PASS: TestConcatStringByAdd (0.00s) == = RUN TestConcatStringByBytesBuffer --- PASS: TestConcatStringByBytesBuffer (0.00s) PASS ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.361s ``` **指定运行某一测试函数 -run** ```go go test -run regexp ``` 只运行 regexp 匹配的函数 ```go PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -run = "TestConcatStringByBytesBuffer" PASS ok goprojects/golang-beginners-guide/src/ch15/benchmark 0.043s ``` **性能测试 -bench** ```go go test -bench regexp ``` go test -bench . "."表示执行包下所有的性能测试函数 ```go PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -bench. goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch15/benchmark cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkConcatStringByAdd-16 11043835 112.1 ns/op BenchmarkConcatStringByBytesBuffer-16 18990766 64.75 ns/op PASS ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.687s ``` **内存测试 -benchmem** ```go go test -bench.-benchmem ``` ```go PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch15\benchmark> go test -bench = "." -benchmem goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch15/benchmark cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkConcatStringByAdd-16 11211181 110.8 ns/op 16 B/op 4 allocs/op BenchmarkConcatStringByBytesBuffer-16 19527594 65.43 ns/op 64 B/op 1 allocs/op PASS ok goprojects/golang-beginners-guide/src/ch15/benchmark 2.751s ``` “16 B/op”表示每一次调用需要分配 16 个字节,“1 allocs/op”表示每一次调用有一次分配。 **自定义测试时间 -benchtime** ```go go test -v -bench =.-benchtime =5s ``` 基准测试框架的默认测试时间为1s,可以通过 -benchtime 参数来指定测试时间。 **开启覆盖测试 -cover** ```go go test -cover ``` ## 23.反射编程 **reflect.TypeOf vs. reflect.ValueOf** - reflect.TypeOf 返回类型(reflect.Type) - reflect.ValueOf 返回值(reflect.Value) - 可以从reflect.Value获得类型 - 通过kind来判断类型 **判断类型——Kind()** ```go type Kind uint const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Pointer Slice String Struct UnsafePointer ) ``` ```go func CheckType(v interface{}) { t := reflect.TypeOf(v) switch t.Kind() { case reflect.Float32, reflect.Float64: fmt.Println("Float") case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: fmt.Println("Integer") default: fmt.Println("Unknown", t) } } func TestBasicType(t *testing.T) { var f float64 = 12 CheckType(f) //Float CheckType(&f) //Unknown *float64 } func TestTypeAndValue(t *testing.T) { var f int64 = 10 t.Log(reflect.TypeOf(f), reflect.ValueOf(f)) //int64 10 t.Log(reflect.ValueOf(f).Type()) //int64 } ``` **利用反射编写灵活的代码** 按名字访问结构的成员 ```go reflect.ValueOf(*e).FieldByName("Name") ``` 按名字访问结构的方法 ```go reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)}) ``` ```go type Employee struct { EmployeeID string Name string Age int } func (e *Employee) UpdateAge(newVal int) { e.Age = newVal } type Customer struct { CookieID string Name string Age int } func TestInvokeByName(t *testing.T) { e := &Employee{ EmployeeID: "1", Name: "Mike", Age: 30, } //按名字获取成员 t.Logf("Name:value(%[1]v), Type(%[1]T)", reflect.ValueOf(*e).FieldByName("Name")) //Name:value(Mike), Type(reflect.Value) if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok { t.Error("Failed to get 'Name' field.") } else { t.Log("Tag:format", nameField.Tag.Get("format")) //Tag:format } reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)}) t.Log("Updated Age:", e) //Updated Age: &{1 Mike 1} } ``` **Struct Tag** ```go type BasicInfo struct { Name string `json:"name"` Age int `json:"age"` } ``` ```go type Employee struct { EmployeeID string Name string `format:"normal"` Age int } func (e *Employee) UpdateAge(newVal int) { e.Age = newVal } type Customer struct { CookieID string Name string Age int } func TestInvokeByName(t *testing.T) { e := &Employee{ EmployeeID: "1", Name: "Mike", Age: 30, } //按名字获取成员 t.Logf("Name:value(%[1]v), Type(%[1]T)", reflect.ValueOf(*e).FieldByName("Name")) //Name:value(Mike), Type(reflect.Value) if nameField, ok := reflect.TypeOf(*e).FieldByName("Name"); !ok { t.Error("Failed to get 'Name' field.") } else { t.Log("Tag:format", nameField.Tag.Get("format")) //Tag:format normal } reflect.ValueOf(e).MethodByName("UpdateAge").Call([]reflect.Value{reflect.ValueOf(1)}) t.Log("Updated Age:", e) //Updated Age: &{1 Mike 1} } ``` **访问StructTag** reflect.Type和reflect.Value都有FieldByName方法,注意二者的区别 reflect.Value的FieldByName方法实现如下: ```go func (v Value) FieldByName(name string) Value { v.mustBe(Struct) if f, ok := toRType(v.typ()).FieldByName(name); ok { return v.FieldByIndex(f.Index) } return Value{} } ``` reflect.Type的FieldByName方法实现如下: ```go func (t *rtype) FieldByName(name string) (StructField, bool) { if t.Kind() != Struct { panic("reflect: FieldByName of non-struct type " + t.String()) } tt := (*structType)(unsafe.Pointer(t)) return tt.FieldByName(name) } ``` **DeepEqual(比较切片和map)** ```go func TestDeepEqual(t *testing.T) { a := map[int]string{1: "one", 2: "two", 3: "three"} b := map[int]string{1: "one", 2: "two", 3: "three"} //t.Log(a == b) //invalid operation: a == b (map can only be compared to nil) t.Log("a==b?", reflect.DeepEqual(a, b)) //a==b? true s1 := []int{1, 2, 3} s2 := []int{1, 2, 3} s3 := []int{2, 3, 1} //t.Log(s1 == s2) //invalid operation: s1 == s2 (slice can only be compared to nil) t.Log("s1==s2?", reflect.DeepEqual(s1, s2)) //s1==s2? true t.Log("s1==s3?", reflect.DeepEqual(s1, s3)) //s1==s3? false } ``` **万能程序** ```go type Customer struct { CookieID string Name string Age int } func fillBySetting(st interface{}, settings map[string]interface{}) error { if reflect.TypeOf(st).Kind() != reflect.Ptr { return errors.New("The first param should be a pointer to the struct type. ") } if reflect.TypeOf(st).Elem().Kind() != reflect.Struct { return errors.New("The first param should be a pointer to ths struct type.") } if settings == nil { return errors.New("settings is nil.") } var ( field reflect.StructField ok bool ) for k, v := range settings { if field, ok = (reflect.ValueOf(st)).Elem().Type().FieldByName(k); !ok { continue } if field.Type == reflect.TypeOf(v) { vstr := reflect.ValueOf(st) vstr = vstr.Elem() vstr.FieldByName(k).Set(reflect.ValueOf(v)) } } return nil } func TestFillNameAndAge(t *testing.T) { settings := map[string]interface{}{"Name": "Mike", "Age": 30} e := Employee{} if err := fillBySetting(&e, settings); err != nil { t.Fatal(err) } t.Log(e) //{ Mike 30} c := new(Customer) if err := fillBySetting(c, settings); err != nil { t.Fatal(err) } t.Log(*c) //{ Mike 30} } ``` ## 24.不安全编程 **不安全行为的危险性** ```go func TestUnsafe(t *testing.T) { i := 10 f := *(*float64)(unsafe.Pointer(&i)) t.Log(unsafe.Pointer(&i)) //0xc00000a3a8 t.Log(f) //5e-323 } ``` **合理的类型转换 ** ```go type MyInt int func TestConvert(t *testing.T) { a := []int{1, 2, 3, 4} b := *(*[]MyInt)(unsafe.Pointer(&a)) t.Log(b) //[1 2 3 4] } ``` ```go // 原子类型操作 func TestAtomic(t *testing.T) { var shareBufPtr unsafe.Pointer writeDataFn := func () { data := []int{} for i := 0; i < 10; i++ { data = append(data, i) } atomic.StorePointer(&shareBufPtr, unsafe.Pointer(&data)) } readDataFn := func () { data := atomic.LoadPointer(&shareBufPtr) fmt.Println(data, *(*[]int)(data)) } var wg sync.WaitGroup writeDataFn() for i := 0; i < 5; i++ { wg.Add(1) go func () { for i := 0; i < 5; i++ { writeDataFn() time.Sleep(time.Microsecond * 100) } wg.Done() }() wg.Add(1) go func () { for i := 0; i < 5; i++ { readDataFn() time.Sleep(time.Microsecond * 100) } wg.Done() }() } wg.Wait() } /* 0xc0000080f0 [0 1 2 3 4 5 6 7 8 9] 0xc000008210 [0 1 2 3 4 5 6 7 8 9] 0xc000008240 [0 1 2 3 4 5 6 7 8 9] 0xc000008288 [0 1 2 3 4 5 6 7 8 9] 0xc000008288 [0 1 2 3 4 5 6 7 8 9] 0xc000200018 [0 1 2 3 4 5 6 7 8 9] 0xc000200018 [0 1 2 3 4 5 6 7 8 9] 0xc000200018 [0 1 2 3 4 5 6 7 8 9] 0xc000200018 [0 1 2 3 4 5 6 7 8 9] 0xc0000082b8 [0 1 2 3 4 5 6 7 8 9] 0xc0000082b8 [0 1 2 3 4 5 6 7 8 9] 0xc000100030 [0 1 2 3 4 5 6 7 8 9] 0xc000100030 [0 1 2 3 4 5 6 7 8 9] 0xc000008330 [0 1 2 3 4 5 6 7 8 9] 0xc000008330 [0 1 2 3 4 5 6 7 8 9] 0xc000100078 [0 1 2 3 4 5 6 7 8 9] 0xc000100078 [0 1 2 3 4 5 6 7 8 9] 0xc0001860f0 [0 1 2 3 4 5 6 7 8 9] 0xc0001860f0 [0 1 2 3 4 5 6 7 8 9] 0xc000200078 [0 1 2 3 4 5 6 7 8 9] 0xc000200078 [0 1 2 3 4 5 6 7 8 9] 0xc000200078 [0 1 2 3 4 5 6 7 8 9] 0xc000200078 [0 1 2 3 4 5 6 7 8 9] 0xc0001000c0 [0 1 2 3 4 5 6 7 8 9] 0xc000008360 [0 1 2 3 4 5 6 7 8 9] */ ``` ## 25.常用架构模式 **pipe-filter架构** ![pipe-filter架构](https://i-blog.csdnimg.cn/direct/130c39ae04d549da96adde6b0e7cee4c.png) - 非常适合于数据处理及数据分析系统 - Filter封装数据处理的功能 - Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据(进程内同步调用时,pipe演变为数据在方法调用间传递) - 松耦合:Filter只跟数据(格式)耦合 **Filter和组合模式** ![filter和组合模式](https://i-blog.csdnimg.cn/direct/19450a66e9f448589ded00d93152fe8b.png) **微内核架构(Micro-Kernel)** **特点** - 易于扩展 - 错误隔离 - 保持架构一致性 **要点** - 内核包含公共流程或通用逻辑 - 将可变或可扩展部分规划为扩展点 - 抽象扩展点行为,定义接口 - 利用插件进行可扩展 ![Micro-Kernel架构](https://i-blog.csdnimg.cn/direct/7a0af07c042d422d90a747d73663005f.png) ## 26.Json解析 **内置的Json解析** 利用反射实现,通过FeildTag来标识对应的json值 ```go type BasicInfo struct { Name string `json:"name"` Age int `json:"age"` } type JobInfo struct { Skills []string `json:"skills"` } type Employee struct { BasicInfo BasicInfo `json:"basic_info"` JobInfo JobInfo `json:"job_info"` } var jsonStr = `{ "basic_info":{ "name":"Mike", "age":30 }, "job_info":{ "skills":["Java","Go","C"] } } ` func TestEmbeddedJson(t *testing.T) { e := new(Employee) err := json.Unmarshal([]byte(jsonStr), e) if err != nil { t.Error(err) } fmt.Println(*e) //{{Mike 30} {[Java Go C]}} if v, err := json.Marshal(e); err == nil { fmt.Println(string(v)) //{"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}} } else { t.Error(err) } } ``` **更快的JSON解析** EasyJSON采用代码生成而非反射 **安装指令**:go get -u github.com/mailru/easyjson/... **使用**:easyjson -all <结构定义>.go 关于easyjson的安装与使用参考[【Go】EasyJson使用](https://blog.csdn.net/smallbirdnq/article/details/134667382) ```go var jsonStr = `{ "basic_info":{ "name":"Mike", "age":30 }, "job_info":{ "skills":["Java","Go","C"] } } ` func TestEmbeddedJson(t *testing.T) { e := new(Employee) err := json.Unmarshal([]byte(jsonStr), e) if err != nil { t.Error(err) } fmt.Println(*e) if v, err := json.Marshal(e); err == nil { fmt.Println(string(v)) } else { t.Error(err) } } func TestEasyJson(t *testing.T) { e := Employee{} e.UnmarshalJSON([]byte(jsonStr)) fmt.Println(e) if v, err := e.MarshalJSON(); err != nil { t.Error(err) } else { fmt.Println(string(v)) } } func BenchmarkEmbeddedJson(b *testing.B) { b.ResetTimer() e := new(Employee) for i := 0; i < b.N; i++ { err := json.Unmarshal([]byte(jsonStr), e) if err != nil { b.Error(err) } if _, err = json.Marshal(e); err != nil { b.Error(err) } } } func BenchmarkEasyJson(b *testing.B) { b.ResetTimer() e := Employee{} for i := 0; i < b.N; i++ { err := e.UnmarshalJSON([]byte(jsonStr)) if err != nil { b.Error(err) } if _, err = e.MarshalJSON(); err != nil { b.Error(err) } } } /* PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch18\easyjson> go test -bench . {{Mike 30} {[Java Go C]}} {"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}} {{Mike 30} {[Java Go C]}} {"basic_info":{"name":"Mike","age":30},"job_info":{"skills":["Java","Go","C"]}} goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch18/easyjson cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkEmbeddedJson-16 495374 2515 ns/op BenchmarkEasyJson-16 1571302 765.1 ns/op PASS ok goprojects/golang-beginners-guide/src/ch18/easyjson 3.294s */ ``` ## 27.HTTP服务 **Handler** ```go type Handler interface { ServeHTTP(ResponseWriter, *Request) } ``` ```go func main() { http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }) http.HandleFunc("/time/", func (w http.ResponseWriter, r *http.Request) { t := time.Now() timeStr := fmt.Sprintf("{\"time\":\"%s\"}", t) w.Write([]byte(timeStr)) }) http.ListenAndServe(":8080", nil) } ``` **路由规则** - URL分为两种,末尾是/:表示一个子树,后面可以跟其他子路径;末尾不是/,表示一个叶子,固定的路径; - 以/结尾的URL可以匹配它的任何子路径,比如/images会匹配/images/cute-cat.jpg; - 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理; - 如果没有找到任何匹配项,会返回404错误。 **更好的Router** ```go func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { fmt.Fprint(w, "Welcome!\n") } func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, "hello %s", ps.ByName("name")) } func main() { router := httprouter.New() router.GET("/", Index) router.GET("/hello/:name", Hello) log.Fatal(http.ListenAndServe(":8080", router)) } ``` ## 28.性能分析 **性能分析工具** - 安装graphviz([graphviz下载地址](https://graphviz.org/download/)) - 将\$GOPATH/bin加入\$PATH - 安装go-torch go get -u github.com/uber/go-torch 下载并复制flamegraph.pl至\$GOPATH/bin路径下 将\$GOPATH/bin加入\$PATH **通过文件方式输出Profile** - 灵活性高,适用于特定代码段的分析 - 通过手动调用runtime/pprof的API - [API相关文档](https://studygolang.com/static/pkgdoc/pkg/runtime_pprof.htm) - go tool pprof [binary] [binary.prof] **具体应用如下** ```go //src/ch21/tools/file/prof.go const ( col = 10000 row = 10000 ) func fillMatrix(m *[row][col]int) { s := rand.New(rand.NewSource(time.Now().UnixNano())) for i := 0; i < row; i++ { for j := 0; j < col; j++ { m[i][j] = s.Intn(100000) } } } func calculate(m *[row][col]int) { for i := 0; i < row; i++ { tmp := 0 for j := 0; j < col; j++ { tmp += m[i][j] } } } func main() { //创建输出文件 f, err := os.Create("cpu.prof") if err != nil { log.Fatal("could not create CPU profile: ", err) } // 获取系统信息 if err := pprof.StartCPUProfile(f); err != nil { //监控cpu log.Fatal("could not start CPU profile: ", err) } defer pprof.StopCPUProfile() // 主逻辑区,进行一些简单的代码运算 x := [row][col]int{} fillMatrix(&x) calculate(&x) f1, err := os.Create("mem.prof") if err != nil { log.Fatal("could not create memory profile: ", err) } runtime.GC() // GC,获取最新的数据信息 if err := pprof.WriteHeapProfile(f1); err != nil { // 写入内存信息 log.Fatal("could not write memory profile: ", err) } f1.Close() f2, err := os.Create("goroutine.prof") if err != nil { log.Fatal("could not create groutine profile: ", err) } if gProf := pprof.Lookup("goroutine"); gProf == nil { log.Fatal("could not write groutine profile: ") } else { gProf.WriteTo(f2, 0) } f2.Close() } ``` 在文件所在路径下执行 ```bash go build .\prof.go .\prof.exe ``` 执行完毕后生成下列文件: ```bash Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2024/7/31 10:23 2894 cpu.prof -a---- 2024/7/31 10:23 1402 goroutine.prof -a---- 2024/7/31 10:23 1219 mem.prof -a---- 2024/7/31 10:23 2785792 prof.exe -a---- 2024/7/27 18:53 1547 prof.go ``` 查看prof文件 ```bash go tool pprof prof .\cpu.prof ``` 执行结果如下 ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\ tools\file> go tool pprof prof prof: open prof: The system cannot find the file specified. failed to fetch any source profiles PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\ tools\file> go tool pprof prof .\cpu.prof prof: open prof: The system cannot find the file specified. Fetched 1 source profiles out of 2 File: prof.exe Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\sr c\ch21\tools\file\prof.exe2024-07-31 10:23:38.9839107 +0800 CST Type: cpu Time: Jul 31, 2024 at 10:23am (CST) Duration: 1.38s, Total samples = 1.19s (86.04%) Entering interactive mode (type "help" for commands, "o" for options) ``` 在pprof环境下查看cpu占用 ```bash (pprof) top Showing nodes accounting for 1.19s, 100% of 1.19s total Showing top 10 nodes out of 44 flat flat% sum% cum cum% 0.76s 63.87% 63.87% 0.87s 73.11% math/rand.(*Rand).Int31n 0.22s 18.49% 82.35% 1.15s 96.64% main.fillMatrix 0.07s 5.88% 88.24% 0.07s 5.88% math/rand.(*rngSource).Uint6 4 (inline) 0.06s 5.04% 93.28% 0.93s 78.15% math/rand.(*Rand).Intn 0.02s 1.68% 94.96% 0.02s 1.68% main.calculate 0.02s 1.68% 96.64% 0.11s 9.24% math/rand.(*Rand).Int63 (inl ine) 0.02s 1.68% 98.32% 0.09s 7.56% math/rand.(*rngSource).Int63 0.01s 0.84% 99.16% 0.01s 0.84% runtime.cgocall 0.01s 0.84% 100% 0.01s 0.84% runtime.memclrNoHeapPointers 0 0% 100% 0.01s 0.84% compress/flate.(*compressor) .init ``` 详细分析某个方法 ```bash (pprof) list fillMatrix Total: 1.19s ROUTINE ======================== main.fillMatrix in D:\Environment\GoPat h\src\goprojects\golang-beginners-guide\src\ch21\tools\file\prof.go 220ms 1.15s (flat, cum) 96.64% of Total . . 17:func fillMatrix(m *[row][col]int) { . . 18: s := rand.New(rand.NewSource(time.Now(). UnixNano())) . . 19: . . 20: for i := 0; i < row; i++ { 10ms 10ms 21: for j := 0; j < col; j++ { 210ms 1.14s 22: m[i][j] = s.Intn(100000) . . 23: } . . 24: } . . 25:} . . 26: . . 27:func calculate(m *[row][col]int) { ``` 图形化方式查看各函数间CPU耗时 ```bash (pprof) svg Generating report in profile001.svg (pprof) exit ``` ![图形化查看各函数CPU耗时](https://i-blog.csdnimg.cn/direct/7cb05c8a17a647b5831854ad1ab518a7.png) ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\ tools\file> go tool pprof prof .\mem.prof prof: open prof: The system cannot find the file specified. Fetched 1 source profiles out of 2 File: prof.exe Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\sr c\ch21\tools\file\prof.exe2024-07-31 10:23:38.9839107 +0800 CST Type: inuse_space Time: Jul 31, 2024 at 10:23am (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 1.16MB, 100% of 1.16MB total flat flat% sum% cum cum% 1.16MB 100% 100% 1.16MB 100% runtime/pprof.StartCPUProfil e 0 0% 100% 1.16MB 100% main.main 0 0% 100% 1.16MB 100% runtime.main (pprof) list main.main Total: 1.16MB ROUTINE ======================== main.main in D:\Environment\GoPath\src\ goprojects\golang-beginners-guide\src\ch21\tools\file\prof.go 0 1.16MB (flat, cum) 100% of Total . . 36:func main() { . . 37: //创建输出文件 . . 38: f, err := os.Create("cpu.prof") . . 39: if err != nil { . . 40: log.Fatal("could not create CPU profile: ", err) . . 41: } . . 42: . . 43: // 获取系统信息 . 1.16MB 44: if err := pprof.StartCPUProfile(f); err != nil { //监控cpu . . 45: log.Fatal("could not start CPU p rofile: ", err) . . 46: } . . 47: defer pprof.StopCPUProfile() . . 48: . . 49: // 主逻辑区,进行一些简单的代码运算 (pprof) ``` **通过HTTP方式输出Profile** - 简单,适用于持续性运行的应用 - 在应用程序中导入import _ "net/http/pprof",并启动http server即可 - http://\:\/debug/pprof/ - go tool pprof http://\:\/debug/pprof/profile?seconds=10(默认值为30秒) - go-torch -seconds 10 http://\:\/debug/pprof/profile ```go package main import ( "fmt" "log" "net/http" _ "net/http/pprof" ) func GetFibonacciSerie(n int) []int { ret := make([]int, 2, n) ret[0] = 1 ret[1] = 1 for i := 2; i < n; i++ { ret = append(ret, ret[i-2]+ret[i-1]) } return ret } func index(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Welcome!")) } func createFBS(w http.ResponseWriter, r *http.Request) { var fbs []int for i := 0; i < 1000000; i++ { fbs = GetFibonacciSerie(50) } w.Write([]byte(fmt.Sprintf("%v", fbs))) } func main() { http.HandleFunc("/", index) http.HandleFunc("/fb", createFBS) log.Fatal(http.ListenAndServe(":8081", nil)) } ``` ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\ tools\file> go tool pprof http://127.0.0.1:8081/debug/pprof/profile Fetching profile over HTTP from http://127.0.0.1:8081/debug/pprof/profil e Saved profile in C:\Users\37471\pprof\pprof.___go_build_goprojects_golan g_beginners_guide_src_ch21_tools_http.exe.samples.cpu.001.pb.gz File: ___go_build_goprojects_golang_beginners_guide_src_ch21_tools_http. exe Build ID: C:\Users\37471\AppData\Local\Temp\GoLand\___go_build_goproject s_golang_beginners_guide_src_ch21_tools_http.exe2024-07-31 13:28:01.5849 09 +0800 CST Type: cpu Time: Jul 31, 2024 at 1:31pm (CST) Duration: 30.01s, Total samples = 290ms ( 0.97%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 260ms, 89.66% of 290ms total Showing top 10 nodes out of 83 flat flat% sum% cum cum% 140ms 48.28% 48.28% 160ms 55.17% main.GetFibonacciSerie 30ms 10.34% 58.62% 30ms 10.34% runtime.stdcall2 20ms 6.90% 65.52% 20ms 6.90% runtime.cgocall 10ms 3.45% 68.97% 10ms 3.45% runtime.(*activeSweep).begin (inline) 10ms 3.45% 72.41% 10ms 3.45% runtime.(*spanSet).reset 10ms 3.45% 75.86% 30ms 10.34% runtime.gcBgMarkWorker 10ms 3.45% 79.31% 10ms 3.45% runtime.osyield 10ms 3.45% 82.76% 10ms 3.45% runtime.pMask.read 10ms 3.45% 86.21% 10ms 3.45% runtime.stdcall3 10ms 3.45% 89.66% 10ms 3.45% runtime.stdcall7 (pprof) top -cum Showing nodes accounting for 150ms, 51.72% of 290ms total Showing top 10 nodes out of 83 flat flat% sum% cum cum% 140ms 48.28% 48.28% 160ms 55.17% main.GetFibonacciSerie (inline) 0 0% 48.28% 160ms 55.17% main.createFBS 0 0% 48.28% 160ms 55.17% net/http.(*ServeMux).ServeHTTP 0 0% 48.28% 160ms 55.17% net/http.(*conn).serve 0 0% 48.28% 160ms 55.17% net/http.HandlerFunc.ServeHTTP 0 0% 48.28% 160ms 55.17% net/http.serverHandler.ServeHTTP 0 0% 48.28% 60ms 20.69% runtime.systemstack 10ms 3.45% 51.72% 30ms 10.34% runtime.gcBgMarkWorker 0 0% 51.72% 30ms 10.34% runtime.lock 0 0% 51.72% 30ms 10.34% runtime.lock2 (pprof) list main.GetFibonacciSerie Total: 290ms ROUTINE ======================== main.GetFibonacciSerie in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch21\tools\http\fb_server.go 140ms 160ms (flat, cum) 55.17% of Total . . 10:func GetFibonacciSerie(n int) []int { . 20ms 11: ret := make([]int, 2, n) . . 12: ret[0] = 1 . . 13: ret[1] = 1 10ms 10ms 14: for i := 2; i < n; i++ { 130ms 130ms 15: ret = append(ret, ret[i-2]+ret[i-1]) . . 16: } . . 17: return ret . . 18:} . . 19: . . 20:func index(w http.ResponseWriter, r *http.Request) { (pprof) ``` ## 29.性能调优 **常见分析指标** - Wall Time - CPU Time - Block Time - Memory allocation - GC times/time spent **go test输出profile** ```go go test -bench.-cpuprofile = cpu.prof go test -bench.-blockprofile = block.prof go tool pprof cpu.prof go help testflag ``` ```go type Request struct { TransactionID string `json:"transaction_id"` PayLoad []int `json:"payload"` } type Response struct { TransactionID string `json:"transaction_id"` Expression string `json:"exp"` } func createRequest() string { payload := make([]int, 100, 100) for i := 0; i < 100; i++ { payload[i] = i } req := Request{"demo_transaction", payload} v, err := json.Marshal(&req) if err != nil { panic(err) } return string(v) } func processRequest(reqs []string) []string { reps := []string{} for _, req := range reqs { reqObj := &Request{} reqObj.UnmarshalJSON([]byte(req)) // json.Unmarshal([]byte(req), reqObj) var buf strings.Builder for _, e := range reqObj.PayLoad { buf.WriteString(strconv.Itoa(e)) buf.WriteString(",") } repObj := &Response{reqObj.TransactionID, buf.String()} repJson, err := repObj.MarshalJSON() //repJson, err := json.Marshal(&repObj) if err != nil { panic(err) } reps = append(reps, string(repJson)) } return reps } func processRequestOld(reqs []string) []string { reps := []string{} for _, req := range reqs { reqObj := &Request{} json.Unmarshal([]byte(req), reqObj) ret := "" for _, e := range reqObj.PayLoad { ret += strconv.Itoa(e) + "," } repObj := &Response{reqObj.TransactionID, ret} repJson, err := json.Marshal(&repObj) if err != nil { panic(err) } reps = append(reps, string(repJson)) } return reps } func TestCreateRequest(t *testing.T) { str := createRequest() t.Log(str) } func TestProcessRequest(t *testing.T) { reqs := []string{} reqs = append(reqs, createRequest()) reps := processRequest(reqs) t.Log(reps[0]) } func BenchmarkProcessRequest(b *testing.B) { reqs := []string{} reqs = append(reqs, createRequest()) b.ResetTimer() for i := 0; i < b.N; i++ { _ = processRequest(reqs) } b.StopTimer() } func BenchmarkProcessRequestOld(b *testing.B) { reqs := []string{} reqs = append(reqs, createRequest()) b.ResetTimer() for i := 0; i < b.N; i++ { _ = processRequestOld(reqs) } b.StopTimer() } ``` **CPU耗时分析** ```bash D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go test -v === RUN TestCreateRequest optimization_test.go:7: {"transaction_id":"demo_transaction","payload":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99]} --- PASS: TestCreateRequest (0.00s) === RUN TestProcessRequest optimization_test.go:14: {"transaction_id":"demo_transaction","exp":"0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,"} --- PASS: TestProcessRequest (0.00s) PASS ok goprojects/golang-beginners-guide/src/ch22 1.605s D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go test -bench . -cpuprofile=cpu.prof goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch22 cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkProcessRequest-16 168573 7218 ns/op BenchmarkProcessRequestOld-16 51066 23125 ns/op PASS ok goprojects/golang-beginners-guide/src/ch22 4.628s D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>dir 驱动器 D 中的卷是 Data 卷的序列号是 40CC-CBCE D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22 的目录 2024/07/31 13:55 . 2024/07/31 13:46 .. 2024/07/31 13:55 5,605,376 ch22.test.exe 2024/07/31 13:55 15,675 cpu.prof 2024/07/27 18:53 700 optimization_test.go 2024/07/27 18:53 1,353 optmization.go 2024/07/27 18:53 246 structs.go 2024/07/27 18:53 4,760 structs_easyjson.go 6 个文件 5,628,110 字节 2 个目录 229,622,636,544 可用字节 D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go tool pprof cpu.prof File: ch22.test.exe Build ID: C:\Users\37471\AppData\Local\Temp\go-build2577889608\b001\ch22.test.exe2024-07-31 13:55:08.6787643 +0800 CST Type: cpu Time: Jul 31, 2024 at 1:55pm (CST) Duration: 2.87s, Total samples = 3.44s (119.67%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top -cum Showing nodes accounting for 0.11s, 3.20% of 3.44s total Dropped 86 nodes (cum <= 0.02s) Showing top 10 nodes out of 175 flat flat% sum% cum cum% 0 0% 0% 2.35s 68.31% testing.(*B).runN 0 0% 0% 2.34s 68.02% testing.(*B).launch 0 0% 0% 1.21s 35.17% goprojects/golang-beginners-guide/src/ch22.BenchmarkProcessRequestOld 0.02s 0.58% 0.58% 1.21s 35.17% goprojects/golang-beginners-guide/src/ch22.processRequestOld 0 0% 0.58% 1.13s 32.85% goprojects/golang-beginners-guide/src/ch22.BenchmarkProcessRequest 0.04s 1.16% 1.74% 1.13s 32.85% goprojects/golang-beginners-guide/src/ch22.processRequest 0 0% 1.74% 0.97s 28.20% goprojects/golang-beginners-guide/src/ch22.(*Request).UnmarshalJSON (partial-inline) 0.05s 1.45% 3.20% 0.97s 28.20% goprojects/golang-beginners-guide/src/ch22.easyjson6a975c40DecodeCh471 0 0% 3.20% 0.92s 26.74% runtime.systemstack 0 0% 3.20% 0.52s 15.12% encoding/json.Unmarshal (pprof) list processRequest Total: 3.44s ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequest in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go 40ms 1.13s (flat, cum) 32.85% of Total . . 22:func processRequest(reqs []string) []string { . . 23: reps := []string{} . . 24: for _, req := range reqs { . . 25: reqObj := &Request{} . 740ms 26: reqObj.UnmarshalJSON([]byte(req)) . . 27: // json.Unmarshal([]byte(req), reqObj) . . 28: . . 29: var buf strings.Builder 20ms 20ms 30: for _, e := range reqObj.PayLoad { 20ms 180ms 31: buf.WriteString(strconv.Itoa(e)) . 30ms 32: buf.WriteString(",") . . 33: } . . 34: repObj := &Response{reqObj.TransactionID, buf.String()} . 120ms 35: repJson, err := repObj.MarshalJSON() . . 36: //repJson, err := json.Marshal(&repObj) . . 37: if err != nil { . . 38: panic(err) . . 39: } . 40ms 40: reps = append(reps, string(repJson)) . . 41: } . . 42: return reps . . 43:} . . 44: . . 45:func processRequestOld(reqs []string) []string { ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequestOld in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go 20ms 1.21s (flat, cum) 35.17% of Total . . 45:func processRequestOld(reqs []string) []string { . . 46: reps := []string{} . . 47: for _, req := range reqs { . 10ms 48: reqObj := &Request{} . 520ms 49: json.Unmarshal([]byte(req), reqObj) . . 50: ret := "" . . 51: for _, e := range reqObj.PayLoad { 10ms 480ms 52: ret += strconv.Itoa(e) + "," . . 53: } . . 54: repObj := &Response{reqObj.TransactionID, ret} . 180ms 55: repJson, err := json.Marshal(&repObj) . . 56: if err != nil { . . 57: panic(err) . . 58: } . 10ms 59: reps = append(reps, string(repJson)) . . 60: } 10ms 10ms 61: return reps . . 62:} (pprof) ``` **内存分析** ```bash D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22>go tool pprof mem.prof File: ch22.test.exe Build ID: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\ch22.test.exe2024-07-31 13:55:13.3139604 +0800 CST Type: alloc_space Time: Jul 31, 2024 at 2:11pm (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 1928.14MB, 99.69% of 1934.14MB total Dropped 21 nodes (cum <= 9.67MB) Showing top 10 nodes out of 29 flat flat% sum% cum cum% 946.19MB 48.92% 48.92% 1157.79MB 59.86% goprojects/golang-beginners-guide/src/ch22.processRequestOld 462.80MB 23.93% 72.85% 464.80MB 24.03% goprojects/golang-beginners-guide/src/ch22.easyjson6a975c40DecodeCh471 153.55MB 7.94% 80.79% 153.55MB 7.94% strings.(*Builder).WriteString (inline) 109.03MB 5.64% 86.42% 775.35MB 40.09% goprojects/golang-beginners-guide/src/ch22.processRequest 92.02MB 4.76% 91.18% 92.02MB 4.76% github.com/mailru/easyjson/buffer.getBuf 81.53MB 4.22% 95.40% 81.53MB 4.22% github.com/mailru/easyjson/buffer.(*Buffer).BuildBytes 50.51MB 2.61% 98.01% 142.53MB 7.37% github.com/mailru/easyjson/buffer.(*Buffer).ensureSpaceSlow 24.01MB 1.24% 99.25% 76.02MB 3.93% encoding/json.Marshal 8.50MB 0.44% 99.69% 135.58MB 7.01% encoding/json.Unmarshal 0 0% 99.69% 124.58MB 6.44% encoding/json.(*decodeState).object (pprof) list processRequest Total: 1.89GB ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequest in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go 109.03MB 775.35MB (flat, cum) 40.09% of Total . . 22:func processRequest(reqs []string) []string { . . 23: reps := []string{} . . 24: for _, req := range reqs { . . 25: reqObj := &Request{} 50.52MB 390.74MB 26: reqObj.UnmarshalJSON([]byte(req)) . . 27: // json.Unmarshal([]byte(req), reqObj) . . 28: . . 29: var buf strings.Builder . . 30: for _, e := range reqObj.PayLoad { . 59.01MB 31: buf.WriteString(strconv.Itoa(e)) . 94.54MB 32: buf.WriteString(",") . . 33: } . . 34: repObj := &Response{reqObj.TransactionID, buf.String()} . 172.54MB 35: repJson, err := repObj.MarshalJSON() . . 36: //repJson, err := json.Marshal(&repObj) . . 37: if err != nil { . . 38: panic(err) . . 39: } 58.52MB 58.52MB 40: reps = append(reps, string(repJson)) . . 41: } . . 42: return reps . . 43:} . . 44: . . 45:func processRequestOld(reqs []string) []string { ROUTINE ======================== goprojects/golang-beginners-guide/src/ch22.processRequestOld in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch22\optmization.go 946.19MB 1.13GB (flat, cum) 59.86% of Total . . 45:func processRequestOld(reqs []string) []string { . . 46: reps := []string{} . . 47: for _, req := range reqs { 1.50MB 1.50MB 48: reqObj := &Request{} 19.51MB 155.09MB 49: json.Unmarshal([]byte(req), reqObj) . . 50: ret := "" . . 51: for _, e := range reqObj.PayLoad { 900.67MB 900.67MB 52: ret += strconv.Itoa(e) + "," . . 53: } 2MB 2MB 54: repObj := &Response{reqObj.TransactionID, ret} . 76.02MB 55: repJson, err := json.Marshal(&repObj) . . 56: if err != nil { . . 57: panic(err) . . 58: } 22.51MB 22.51MB 59: reps = append(reps, string(repJson)) . . 60: } . . 61: return reps . . 62:} (pprof) ``` ## 30.别被性能锁住 ```go var cache map[string]string const NUM_OF_READER int = 40 const READ_TIMES = 100000 func init() { cache = make(map[string]string) cache["a"] = "aa" cache["b"] = "bb" } func lockFreeAccess() { var wg sync.WaitGroup wg.Add(NUM_OF_READER) for i := 0; i < NUM_OF_READER; i++ { go func () { for j := 0; j < READ_TIMES; j++ { _, err := cache["a"] if !err { fmt.Println("Nothing") } } wg.Done() }() } wg.Wait() } func lockAccess() { var wg sync.WaitGroup wg.Add(NUM_OF_READER) m := new(sync.RWMutex) for i := 0; i < NUM_OF_READER; i++ { go func () { for j := 0; j < READ_TIMES; j++ { m.RLock() _, err := cache["a"] if !err { fmt.Println("Nothing") } m.RUnlock() } wg.Done() }() } wg.Wait() } func BenchmarkLockFree(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { lockFreeAccess() } } func BenchmarkLock(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { lockAccess() } } ``` ```bash D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock>go test -bench . -cpuprofile=cpu.prof goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch23/lock cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkLockFree-16 493 2348541 ns/op BenchmarkLock-16 14 76886436 ns/op PASS ok goprojects/golang-beginners-guide/src/ch23/lock 4.467s D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock>go tool pprof cpu.prof File: lock.test.exe Build ID: C:\Users\37471\AppData\Local\Temp\go-build263731560\b001\lock.test.exe2024-07-31 14:47:29.0027346 +0800 CST Type: cpu Time: Jul 31, 2024 at 2:47pm (CST) Duration: 2.75s, Total samples = 33.55s (1218.32%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top Showing nodes accounting for 33.44s, 99.67% of 33.55s total Dropped 42 nodes (cum <= 0.17s) flat flat% sum% cum cum% 16.33s 48.67% 48.67% 16.33s 48.67% sync/atomic.(*Int32).Add (inline) 13.78s 41.07% 89.75% 14.56s 43.40% runtime.mapaccess2_faststr 2.14s 6.38% 96.13% 16.98s 50.61% goprojects/golang-beginners-guide/src/ch23/lock_test.lockFreeAccess.func1 0.78s 2.32% 98.45% 0.78s 2.32% runtime.add (inline) 0.34s 1.01% 99.46% 0.34s 1.01% sync.(*WaitGroup).Done (inline) 0.04s 0.12% 99.58% 16.46s 49.06% goprojects/golang-beginners-guide/src/ch23/lock_test.lockAccess.func1 0.02s 0.06% 99.64% 6.43s 19.17% sync.(*RWMutex).RLock (inline) 0.01s 0.03% 99.67% 9.93s 29.60% sync.(*RWMutex).RUnlock (inline) (pprof) list lockAccess Total: 33.55s ROUTINE ======================== goprojects/golang-beginners-guide/src/ch23/lock_test.lockAccess.func1 in D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch23\lock\lock_test.go 40ms 16.46s (flat, cum) 49.06% of Total . . 45: go func() { 20ms 20ms 46: for j := 0; j < READ_TIMES; j++ { . . 47: . 6.43s 48: m.RLock() 10ms 70ms 49: _, err := cache["a"] 10ms 10ms 50: if !err { . . 51: fmt.Println("Nothing") . . 52: } . 9.93s 53: m.RUnlock() . . 54: } . . 55: wg.Done() . . 56: }() . . 57: } . . 58: wg.Wait() (pprof) ``` **sync.Map** - 适合读多写少,且Key相对稳定对的环境 - 采用了空间换时间的方案,并且采用指针的方式间接实现值的映射,所以存储空间会较built-in map大 **Concurrent Map** - 适用于读写都很频繁的情况 **别被性能锁住** - 减少锁的影响范围 - 减少发生锁冲突的概率 sync.Map ConcurrentMap - 避免锁的使用 ## 31.GC友好的代码 **避免内存分配和复制** - 复杂对象尽量传递引用 - 数组的传递 - 结构体传递 **打开GC日志** 只要在程序执行之前加上环境变量GODEBUG=gctrace=1, 如:GODEBUG=gctrace=1 go test -bench . GODEBUG=gctrace=1 go run main.go [日志详细信息参考](https://godoc.org/runtime) ![日志说明](https://i-blog.csdnimg.cn/direct/3493955a2dce48d0b6cdd58622a6739f.png) ```go package gc_friendly import ( "testing" ) const NumOfElems = 1000 type Content struct { Detail [10000]int } func withValue(arr [NumOfElems]Content) int { // fmt.Println(&arr[2]) return 0 } func withReference(arr *[NumOfElems]Content) int { //b := *arr // fmt.Println(&arr[2]) return 0 } func TestFn(t *testing.T) { var arr [NumOfElems]Content //fmt.Println(&arr[2]) withValue(arr) withReference(&arr) } func BenchmarkPassingArrayWithValue(b *testing.B) { var arr [NumOfElems]Content b.ResetTimer() for i := 0; i < b.N; i++ { withValue(arr) } b.StopTimer() } func BenchmarkPassingArrayWithRef(b *testing.B) { var arr [NumOfElems]Content b.ResetTimer() for i := 0; i < b.N; i++ { withReference(&arr) } b.StopTimer() } ``` **设置GODEBUG环境变量为gctrace=1** ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithValue gc 1 @0.068s 0%: 0+0.52+0 ms clock, 0+0/0.52/0.52+0 ms cpu, 4->4->0 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 2 @0.069s 0%: 0+1.5+0 ms clock, 0+0.52/1.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 3 @0.072s 0%: 0.52+1.0+0 ms clock, 8.3+0/0/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 4 @0.074s 0%: 0+1.0+0 ms clock, 0+0/0/0.51+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 5 @0.076s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 6 @0.079s 0%: 0+0.51+0 ms clock, 0+0/1.5/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 7 @0.081s 1%: 0+2.1+0 ms clock, 0+1.0/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 8 @0.084s 1%: 0+1.0+0 ms clock, 0+0.51/2.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 9 @0.086s 1%: 0.51+1.0+0 ms clock, 8.2+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 10 @0.088s 2%: 0+1.0+0 ms clock, 0+1.0/3.6/1.0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 11 @0.091s 2%: 0+1.0+0 ms clock, 0+0.054/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 12 @0.093s 2%: 0+1.5+0 ms clock, 0+1.5/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 13 @0.096s 2%: 0+1.0+0 ms clock, 0+0/2.0/0.51+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 14 @0.100s 2%: 0+1.1+0 ms clock, 0+0/2.6/1.6+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 15 @0.103s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 16 @0.105s 2%: 0+0.51+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 17 @0.107s 2%: 0+1.0+0 ms clock, 0+1.0/2.0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 18 @0.109s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 19 @0.111s 2%: 0+1.0+0 ms clock, 0+0/0.53/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 20 @0.114s 2%: 0+0.51+0 ms clock, 0+0/1.5/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 21 @0.116s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 22 @0.118s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 23 @0.120s 2%: 0+1.1+0 ms clock, 0+0/2.3/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 24 @0.123s 2%: 0+0.50+0 ms clock, 0+1.0/2.0/1.5+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 25 @0.124s 2%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 3->5->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 26 @0.126s 2%: 0+1.3+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 27 @0.130s 2%: 0+1.0+0 ms clock, 0+1.0/4.0/6.0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 28 @0.133s 2%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 29 @0.134s 3%: 1.5+0.56+0 ms clock, 24+0.056/0.056/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 30 @0.138s 3%: 0+0.51+0 ms clock, 0+0.51/2.0/4.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 31 @0.142s 3%: 0+1.0+0 ms clock, 0+0.51/3.1/0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 32 @0.147s 3%: 0+0.51+0 ms clock, 0+1.0/2.0/2.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 33 @0.152s 3%: 0+0.51+0 ms clock, 0+0/2.0/2.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 34 @0.160s 3%: 0+1.0+0 ms clock, 0+0.55/2.1/1.0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 35 @0.170s 3%: 0+2.0+0 ms clock, 0+0/4.0/0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 36 @0.181s 3%: 0+1.0+0 ms clock, 0+0/1.0/1.0+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 37 @0.204s 3%: 0+0.70+0 ms clock, 0+0.093/0.67/1.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 38 @0.215s 2%: 0+1.0+0 ms clock, 0+0/2.1/1.5+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 39 @0.222s 3%: 0.53+0.54+0 ms clock, 8.5+0/1.0/2.1+0 ms cpu, 3->3->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 40 @0.231s 3%: 0+1.0+0 ms clock, 0+2.1/2.1/0+0 ms cpu, 3->4->1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 41 @0.233s 3%: 0+1.5+0 ms clock, 0+0/0/0+0 ms cpu, 3->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 42 @0.237s 3%: 0+1.0+0 ms clock, 0+0.50/2.0/0+0 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 43 @0.240s 3%: 0+0.99+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 44 @0.242s 3%: 0+0.99+0 ms clock, 0+0/2.9/0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 45 @0.244s 3%: 0+0.99+0 ms clock, 0+0/0.99/0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 46 @0.246s 3%: 0+1.1+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 47 @0.248s 3%: 0+1.2+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 48 @0.251s 3%: 0+1.2+1.0 ms clock, 0+0/5.1/3.8+16 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 49 @0.254s 3%: 0+0.99+0.50 ms clock, 0+0/3.9/2.9+8.1 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 50 @0.257s 3%: 0+1.0+0 ms clock, 0+0/4.0/1.0+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 51 @0.259s 3%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 52 @0.262s 3%: 0+1.5+0 ms clock, 0+0/6.0/7.5+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 53 @0.266s 3%: 0+1.0+0 ms clock, 0+0/4.0/6.0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 54 @0.271s 3%: 0+1.0+0 ms clock, 0+0/1.0/0+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 55 @0.285s 3%: 0.061+0.50+0 ms clock, 0.97+0/1.0/1.5+0 ms cpu, 4->4->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 56 @0.304s 3%: 0.53+1.3+0 ms clock, 8.6+0.19/3.7/3.1+0 ms cpu, 4->5->2 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 57 @0.311s 3%: 0+1.6+0 ms clock, 0+0/4.8/5.3+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 58 @0.317s 3%: 0+1.0+0 ms clock, 0+0/3.1/3.1+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 59 @0.322s 3%: 0+1.0+0 ms clock, 0+0/0/0.53+0 ms cpu, 4->6->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 60 @0.325s 3%: 0+1.1+0 ms clock, 0+0/2.5/1.4+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 61 @0.327s 3%: 0+1.0+0 ms clock, 0+1.0/0/0+0 ms cpu, 6->6->2 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 62 @0.331s 3%: 0+0.65+0 ms clock, 0+0/2.6/2.7+0 ms cpu, 5->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 63 @0.333s 3%: 0.52+1.0+0 ms clock, 8.4+0/0/0+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 64 @0.335s 3%: 0+1.0+0 ms clock, 0+2.2/4.3/1.1+0 ms cpu, 6->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 65 @0.339s 3%: 0+0.52+0 ms clock, 0+0/1.0/1.5+0 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 66 @0.348s 3%: 0+1.0+0 ms clock, 0+0/2.6/2.5+0 ms cpu, 4->5->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 67 @0.363s 3%: 0+2.0+0 ms clock, 0+2.0/5.1/2.0+0 ms cpu, 4->6->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 68 @0.501s 2%: 0+1.2+0 ms clock, 0+0/2.8/3.3+0 ms cpu, 5->5->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 69 @0.507s 2%: 0+1.8+0 ms clock, 0+1.0/5.7/2.3+0 ms cpu, 6->6->2 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 70 @0.530s 2%: 0+1.1+0 ms clock, 0+0/4.5/2.8+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 71 @0.557s 2%: 0+1.0+0 ms clock, 0+0.51/2.0/2.5+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 72 @0.563s 2%: 0+0.69+0 ms clock, 0+0/2.7/4.1+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 73 @0.571s 2%: 0+1.3+0 ms clock, 0+0/5.3/2.6+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 74 @0.580s 2%: 0+1.1+0 ms clock, 0+0/4.3/9.2+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 75 @0.589s 2%: 0+1.0+0 ms clock, 0+0/4.1/6.1+0 ms cpu, 5->6->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 76 @0.595s 2%: 0+0+1.5 ms clock, 0+0/0/0+24 ms cpu, 5->6->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 77 @0.601s 2%: 0+1.0+0 ms clock, 0+1.0/4.0/6.0+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 78 @0.608s 2%: 0+1.0+0 ms clock, 0+0/4.0/10+0 ms cpu, 5->5->2 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 79 @0.617s 2%: 0+1.0+0 ms clock, 0+0/2.0/0+0 ms cpu, 5->7->3 MB, 6 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 80 @0.626s 2%: 0+1.0+0 ms clock, 0+0/4.3/9.8+0 ms cpu, 6->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 81 @0.699s 2%: 0+2.4+0 ms clock, 0+0/6.0/0.50+0 ms cpu, 5->6->3 MB, 7 MB goal, 0 MB stacks, 0 MB globals, 16 P # goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref.test gc 1 @0.008s 1%: 0+1.0+0 ms clock, 0+0.51/2.0/1.0+0 ms cpu, 5->5->4 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 2 @0.013s 1%: 0+0.51+0 ms clock, 0+0/1.0/0+0 ms cpu, 20->20->20 MB, 20 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 3 @0.039s 0%: 0+1.5+0 ms clock, 0+0.52/1.0/0+0 ms cpu, 41->41->39 MB, 41 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 1 @0.008s 0%: 0+0.52+0 ms clock, 0+0/0/0+0 ms cpu, 77->77->76 MB, 77 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 2 @0.009s 0%: 0+0.52+0 ms clock, 0+0/1.5/0+0 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 3 @0.055s 1%: 1.0+0+0 ms clock, 16+0/0/0+0 ms cpu, 153->153->0 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P (forced) gc 4 @0.058s 1%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 0->0->0 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 16 P (forced) gc 5 @0.067s 1%: 0+0.98+0 ms clock, 0+0/0/0+0 ms cpu, 76->76->76 MB, 76 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 6 @0.079s 2%: 0+0+1.0 ms clock, 0+0/0/0+17 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkPassingArrayWithValue-16 gc 7 @0.095s 2%: 0+0.50+0 ms clock, 0+0/0.50/0+0 ms cpu, 152->152->0 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P (forced) gc 8 @0.106s 2%: 0+1.0+0 ms clock, 0+1.0/0/0+0 ms cpu, 76->76->76 MB, 76 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 9 @0.115s 1%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 152->152->152 MB, 153 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 10 @0.161s 1%: 0+0.98+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 11 @0.225s 1%: 0+1.5+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 12 @0.275s 0%: 0+1.5+0 ms clock, 0+0/1.5/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 13 @0.323s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 14 @0.374s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 15 @0.421s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 16 @0.467s 0%: 0+1.2+0 ms clock, 0+0/1.2/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 17 @0.515s 0%: 0+1.5+0 ms clock, 0+0/1.5/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 18 @0.565s 0%: 0+1.0+0 ms clock, 0+0/3.1/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 19 @0.613s 0%: 0+0.96+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 20 @0.673s 0%: 0+0.99+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 21 @0.724s 0%: 0+1.0+0 ms clock, 0+0/2.0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 22 @0.778s 0%: 0+4.5+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 23 @0.828s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 24 @0.874s 0%: 0+0+1.0 ms clock, 0+0/0/0+16 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 25 @0.926s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 26 @0.971s 0%: 0+0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 27 @1.020s 0%: 0+1.0+0 ms clock, 0+0/4.0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 28 @1.065s 0%: 1.0+12+0 ms clock, 16+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 29 @1.111s 0%: 0+1.0+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P gc 30 @1.160s 0%: 0+13+0 ms clock, 0+0/0/0+0 ms cpu, 305->305->152 MB, 306 MB goal, 0 MB stacks, 0 MB globals, 16 P 44 24867116 ns/op PASS ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 2.682s ``` **生成trace文件** ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace _ref goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkPassingArrayWithRef-16 1000000000 0.2363 ns/op PASS ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 0.413s PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace _ref.out no required module provides package .out; to add it: go get .out PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithRef -trace=trace _ref goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkPassingArrayWithRef-16 1000000000 0.2473 ns/op PASS ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 0.451s PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go test -bench=BenchmarkPassingArrayWithValue -trace=tra ce_value goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkPassingArrayWithValue-16 49 25593561 ns/op PASS ok goprojects/golang-beginners-guide/src/ch24/gc_friendly/passing_ref 1.406s PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> dir 目录: D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2024/7/27 18:53 799 pass_array_test.go -a---- 2024/7/31 16:16 18433 trace_ref -a---- 2024/7/31 16:16 32907 trace_value ``` **go tool trace** 普通程序输出trace信息 ```go func main(){ f, err := os.Create("trace.out") if err != nil{ panic(err) } defer f.Close() err = trace.Start(f) if err != nil{ panic(err) } defer trace.Stop() //你的程序 } ``` 测试程序输出trace信息 go test -trace trace.out 可视化trace信息: go tool trace trace.out ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24\gc_friendly\passing_ref> go tool trace trace_ref 2024/07/31 16:18:48 Preparing trace for viewer... 2024/07/31 16:18:48 Splitting trace for viewer... 2024/07/31 16:18:48 Opening browser. Trace viewer is listening on http://127.0.0.1:13904 ``` **避免内存分配和复制** - 初始化至合适的大小(自动扩容是有代价的) - 复用内存 ## 32.高效的字符串连接 ```go const numbers = 100 func BenchmarkSprintf(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var s string for i := 0; i < numbers; i++ { s = fmt.Sprintf("%v%v", s, i) } } b.StopTimer() } func BenchmarkStringBuilder(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var builder strings.Builder for i := 0; i < numbers; i++ { builder.WriteString(strconv.Itoa(i)) } _ = builder.String() } b.StopTimer() } func BenchmarkBytesBuf(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var buf bytes.Buffer for i := 0; i < numbers; i++ { buf.WriteString(strconv.Itoa(i)) } _ = buf.String() } b.StopTimer() } func BenchmarkStringAdd(b *testing.B) { b.ResetTimer() for idx := 0; idx < b.N; idx++ { var s string for i := 0; i < numbers; i++ { s += strconv.Itoa(i) } } b.StopTimer() } ``` 测试结果: ```bash PS D:\Environment\GoPath\src\goprojects\golang-beginners-guide\src\ch24> go test -bench . goos: windows goarch: amd64 pkg: goprojects/golang-beginners-guide/src/ch24 cpu: AMD Ryzen 7 4800U with Radeon Graphics BenchmarkSprintf-16 61113 18167 ns/op BenchmarkStringBuilder-16 1478074 802.7 ns/op BenchmarkBytesBuf-16 1325361 915.6 ns/op BenchmarkStringAdd-16 180688 6844 ns/op ``` 常用脚本: ```bash #!/bin/bash # 指定搜索的起始目录 SEARCH_DIR="/path/to/search" # 指定移动到的目标目录 TARGET_DIR="/path/to/target" # 确保目标目录存在,如果不存在则创建 mkdir -p "$TARGET_DIR" # 查找所有包含"aarch64"的目录,并将它们移动到目标目录,然后压缩 find "$SEARCH_DIR" -type d -name "*aarch64*" -exec bash -c ' # 提取找到的目录的完整路径 dir_path="$0" # 构造移动后的目录路径 new_dir_path="${TARGET_DIR}/$(basename "$dir_path")" # 移动目录 mv "$dir_path" "$new_dir_path" # 压缩移动后的目录 tar -czf "${new_dir_path}.tar.gz" "$new_dir_path" ' {} \; echo "处理完成。" ```