Golang篇-Golang学习笔记

image.png

本篇主要记录学习Golang过程的笔记

简介

Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。

Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。

Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。

Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。

因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。

此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。

安装

1.下载安装包

https://go.dev/doc/install

2.查看版本

1
go version

语法

关键字

break 常用于跳出循环,switch default 配合switch、select func 定义函数 interface 接口 select 选择流程
case 配合switch defer 后置处理 go 并行 map 字典 struct 结构体
chan 管道 else 否则 goto 跳到某个代码块 package switch 分支
const 常量 fallthrough 搭配switch if 判断 range 范围 type 类型声明
continue 结束一次循环 for 循环 import 导包 return 返回 var 定义变量

基本类型

Go语言的基本类型有:

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128(复数)
整数类型 备注

有符号 | int | 所占用的字节数与运行机器的CPU相关。在32位机器中,大小为4字节;在64位机器中,大小为8字节 |
| | int8 | 占用一个字节存储(8位),范围是-128 ~ 127 |
| | int16 | 占用两个字节存储(16位),范围是-32768 ~ 32767 |
| | int32 | 占用四个字节存储(32位),范围是-2147483648 ~ 2147483647 |
| | int64 | 占用八个字节存储(64位),范围是-9223372036854775808 ~ 9223372036854775807 |
|

无符号 | uint | 所占用的字节数与运行机器的CPU相关。在32位机器中,大小为4字节;在64位机器中,大小为8字节 |
| | uint8 | 占用一个字节存储(即8位),范围是0 ~ 255 |
| | uint16 | 占用两个字节存储(16位),范围是0 ~ 65535 |
| | uint32 | 占用四个字节存储(32位),范围是0 ~ 4294967295 |
| | uint64 | 占用八个字节存储(64位),范围是0 ~ 18446744073709551615 |
| | uintptr | 一个无符号整数类型 |
| | byte | byte类型是uint8的别名 |

命名规范

在go中是区分大小写的,属性首字母大写表示公开,小写表示私有

变量声明

变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写

1
var name type
1
name := method or ...
1
2
3
4
5
6
7
8
9
10
var a,b = 1,2
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)

函数声明

image.png

1
2
3
4
5
6
7
8
9
func Hello(name string) string {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
//注意 golang支持返回多个结果
func moreRet(s string) (string,string) {
return s,s
}

变量赋值

1
2
3
声明时赋值
a:=100
var a = 100

变量交换

1
2
3
4
var a int = 100
var b int = 200
b, a = a, b
fmt.Println(a, b)

匿名变量

在golang中_表示空白标识符

1
2
3
4
5
6
7
8
9
10
func main() {
a,_:=m()
fmt.Println(a)
_,b:=m()
fmt.Println(b)
}

func m() (int, int) {
return 1,2
}

类型转换

1
2
3
4
5
6
7
8
func m() (int, int) {
a:=1000
b:=string(a)
fmt.Println(b)
c:=int(b[0])
fmt.Println(c)
return 1,2
}

指针类型

1.创建指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var s = "hello"
fmt.Printf("s=%s\n", s)
ptr:=&s//方式1
fmt.Printf("ptr type=%T\n", ptr)
fmt.Printf("ptr=%p\n", ptr)
val := *ptr
fmt.Println(val)

ptr2 := new(string)//方式二
*ptr2 = "hello"
fmt.Printf("ptr2 type=%T\n", ptr2)
fmt.Println("ptr2 value=",*ptr2)
}

逃逸分析

todo

定义常量

1
2
3
4
5
6
7
const name = "mike"
name = "hello"//cannot assign to name (untyped string constant "mike")
const intro string = "ffffff"
const (
s = 'a'
f float32 = 1
)

类型别名

1
2
3
4
5
6
7
8
9
10
11
12
type Car struct {
No string
Intro string
driver string
}

type Bus = Car

bus := Bus{
No: "粤B",
Intro: "abc",
}

注释使用

1
2
3
4
5
//单行
/*
多行
*/

godoc 工具

1
go get golang.org/x/tools/cmd/godoc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Usage of [go] doc:
go doc
go doc <pkg>
go doc <sym>[.<methodOrField>]
go doc [<pkg>.]<sym>[.<methodOrField>]
go doc [<pkg>.][<sym>.]<methodOrField>
go doc <pkg> <sym>[.<methodOrField>]
For more information run
go help doc

Flags:
-all
show all documentation for package
-c symbol matching honors case (paths not affected)
-cmd
show symbols with package docs even if package is a command
-short
one-line representation for each symbol
-src
show source code for symbol
-u show unexported symbols as well as exported

依赖管理

go使用的是go mod进行依赖管理

Modules 是相关 Go 包的集合,是源代码交换和版本控制的单元。Go语言命令直接支持使用 Modules,包括记录和解析对其他模块的依赖性,Modules 替换旧的基于 GOPATH 的方法,来指定使用哪些源文件。

开启

1
export GO111MODULE=on 或者 export GO111MODULE=auto

详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

go mod <command> [arguments]

The commands are:

download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

分配内存

image.png

流程控制

分支语句(if 和 switch)、循环(for)和跳转(goto)语句。另外,还有循环控制语句(break 和 continue),前者的功能是中断循环或者跳出 switch 判断,后者的功能是继续 for 的下一个循环

1
2
3
4
5
6
7
8
9
switch 接口变量.(type) {
case 类型1:
// 变量是类型1时的处理
case 类型2:
// 变量是类型2时的处理

default:
// 变量不是所有case中列举的类型时的处理
}
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

func main() {
expr:=2000
switch expr {
case 100:
fmt.Println(1)
break
case 200:
fmt.Println(2)
break
default:
fmt.Println("default")
}
}

func fore() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
arr:=[]int{1,2,3,4}
for i, v := range arr {
fmt.Println(i,v)
}
i:=10
for i>0 {
fmt.Println(i)
i--
if i == 7 {
continue
}
if i == 5 {
break
}
}
}
func branch() {
a := 100
if a == 10 {
fmt.Println("case1")
} else if a == 20 {
fmt.Println("case2")
} else {
fmt.Println("case3")
}
}

函数

1.函数定义

1
2
3
func 函数名(形式参数列表)(返回值列表){
函数体
}
1
2
3
4
5
6
7
8
9
10
11
12
func m() (int, int) {
a:=1000
b:=string(a)
fmt.Println(b)
c:=int(b[0])
fmt.Println(c)
return 1,2
}

func f(i, j, k int, s, t string) { /* ... */ }
//等价于
func f(i int, j int, k int, s string, t string) { /* ... */ }

2.函数返回值

1
golang支持多返回值

3.函数也可以作为参数传递

1
2
3
4
5
6
7
8
9
10
func main() {
s := exec(func(s string) string {
return s+" Mike"
},"hello")
fmt.Println(s)
}

func exec(f func(s string) string,s string) string {
return f(s)
}

4.带有变量名的返回值

1
2
3
4
5
6
7
8
9
10
func m2() (a, b int) {
a =1000
b =200
return
}

func m3() {
a,b := m2()
fmt.Println(a,b)
}

5.函数变量

1
2
3
4
5
6
7
8
func main() {
var f func()
f = m
f()
}
func m(){
fmt.Println("func")
}

6.匿名函数

1
2
3
func(参数列表)(返回参数列表){
函数体
}
1
2
3
4
5
6
func main() {
ret:=func() string {
return "Hi"
}()
fmt.Println(ret)
}
1
2
3
4
5
6
7
func main() {
f := func() string {
return "Hi"
}
ret := f()
fmt.Println(ret)
}

参数

1.可变参数

1
2
3
4
5
func m(args... int) {
for i, v := range args {
fmt.Println(i,v)
}
}

闭包

函数 + 引用环境 = 闭包
闭包(Closure)在某些编程语言中也被称为 Lambda 表达式。

定义:在函数内部引用了函数内部变量的函数

闭包内修改变量

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
// 准备一个字符串
str := "hello world"
// 创建一个匿名函数
foo := func() {
// 匿名函数中访问str
str = "hello golang"
}
// 调用匿名函数
foo()
fmt.Println(str)
}

闭包的记忆

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
func main() {
// 创建一个累加器, 初始值为1
accumulator := acc(1)
// 累加1并打印
fmt.Println(accumulator())
fmt.Println(accumulator())
// 打印累加器的函数地址
fmt.Printf("%p\n", &accumulator)
// 创建一个累加器, 初始值为1
accumulator2 := acc(10)
// 累加1并打印
fmt.Println(accumulator2())
// 打印累加器的函数地址
fmt.Printf("%p\n", &accumulator2)
}

// 提供一个值, 每次调用函数会指定对值进行累加
func acc(value int) func() int {
// 返回一个闭包
return func() int {
// 累加
value++
// 返回一个累加值
return value
}
}

defer其实也是一种闭包

后置

defer关键字作为函数的后置执行,在return之前执行,主要用于释放资源

1
2
3
4
5
6
7
8
9
10
func acc(args... int) {
//多个def是按照栈的执行顺序
defer fmt.Println("defer")
defer func() {
fmt.Println("后置函数")
}()
for i, v := range args {
fmt.Println(i,v)
}
}

异常

Error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func Hello(name string) (string, error) {
//如果name为空串丢出错误
if name == "" {
return "", errors.New("empty name")
}
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}

func main() {
message, err := Hello("")
//如果有错误err对象不为nil
if err != nil {
log.Fatal(err)
}
fmt.Println(message)
}

自定义错误

1
2
3
4
func main() {
e := errors.New("xxxx")
fmt.Println(e)
}

Panic

panic手动触发宕机,让程序崩溃,这样开发者可以及时地发现错误

1
2
3
4
5
6
7
8
func main() {
defer fmt.Println("宕机后要做的事情1")
defer fmt.Println("宕机后要做的事情2")
var s string
if s == "" {
panic("s is nil")
}
}

Recover

让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入错误,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。

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

func handlePanic() {
if a := recover(); a != nil {
fmt.Println("RECOVER", a)
}
}

func entry(lang *string, name *string) {
defer handlePanic()
if lang == nil {
panic("Error: Language cannot be nil")
}
if name == nil {
panic("Error: Author name cannot be nil")
}
fmt.Printf("Author Language: %s \n Author Name: %s\n", *lang, *name)
fmt.Printf("Return successfully from the entry function")
}

// Main function
func main() {
A_lang := "GO Language"
entry(&A_lang, nil)
fmt.Printf("Return successfully from the main function")
}

结构

结构体

定义

1
2
3
4
5
type 类型名 struct {
字段1 字段1类型
字段2 字段2类型

}

案例

1
2
3
4
5
type Person struct {
Name string//首字母大写表示public,小写表示private
Sex int16
Address,Phone string
}

实例化

1.实例化结构体类型

1
2
3
4
5
6
var ins T
//or
ins := Struct{
XXX: XXX
...
}

案例

1
2
3
4
5
p1 := Person{
Name: "Mike",
Sex: 1,
Address: "Guangdong province",
}

2.创建指针类型

1
ns := new(T)

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Person struct {
Name string
Sex int16
Address,Phone string
}

func main() {
p2 := new(Person)
p2.Phone = "122121"
p2.Sex = 0
p2.Name = "Lily"
fmt.Printf("%p\n",p2)
fmt.Printf("%T\n",p2)
fmt.Printf("%v\n",*p2)
}

3.取结构体地址实例化

1
2
3
4
5
6
7
p3 := &Person{
Name: "Leo",
Phone: "121212",
Sex: 1,
Address: "xxxxx",
}
fmt.Println(p3)

构造函数

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
package main

import "fmt"

type Person struct {
Name string
Sex int16
Address,Phone string
}

func NewPersonName(name string) *Person {
return &Person{
Name: name,
}
}

func NewPersonNameWithPhone(name ,phone string) *Person {
return &Person{
Name: name,
Phone: phone,
}
}

func main() {
p1:=NewPersonName("mike")
fmt.Println(p1)
p2:=NewPersonNameWithPhone("Leo","182121221")
fmt.Println(p2)
}

匿名属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Person struct {
Name string
int16//匿名字段,当前结构体有且只有一种同一匿名列席,赋值直接使用类型赋值
Address,Phone string
bool
}
func NewPersonNameWithPhone(name ,phone string) *Person {
return &Person{
Name: name,
Phone: phone,
int16: 1,
bool:false,
}
}

容器

数组

1
2
3
var 数组变量名 [元素数量]Type

var arr [10]int
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
func main() {
//定义
var arr [10]int
arr2 := [10]int{10,9,8,7,6,5,4,3,2,1}
arr3 := [...]int{1,2,3,4,5,6,7,8,9,10}
arr4 := []int{5}
arr5 := [][]int{{1,2,3},{4,5,6}}
for i := 0; i < 10; i++ {
arr[i] = i
}
//遍历
for i := 0; i < 10; i++ {
fmt.Print(arr[i])
}
fmt.Println()
for i, v := range arr {
fmt.Printf("%d,%d\n",i,v)
}
fmt.Println()
for _, v := range arr {
fmt.Printf("%d\n",v)
}
//比较(必须是长度一致)
fmt.Println(arr == arr2,arr2 == arr3)
fmt.Println(arr3 == arr4)//Error:Invalid operation: arr3 == arr4 (mismatched types [10]int and []int)
}

切片

Go 语言切片是对数组的抽象(基于数组)。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
//定义一个切片
arr := make([]int,10)
fmt.Println(arr)
for i := 0; i < 10; i++ {
arr[i] = i
}
fmt.Println(len(arr))
fmt.Println(arr)
array:=[]int{1,2,3,4,5,6}
//对数组进行切片
fmt.Println(array[1:4])
}

追加切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
//定义一个切片
arr := make([]int,10)
fmt.Println(arr)
for i := 0; i < 15; i++ {
arr = append(arr,i)//追加
arr = append(arr,[]int{1,2,3}...)//追加切片
arr = append(arr[:2],[]int{1,2,3}...)//指定位置追加
fmt.Printf("cap=%d\n",cap(arr))
fmt.Println(arr)
}
fmt.Println(len(arr))
fmt.Println(arr)
}

切片复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
//定义一个切片
arr := make([]int,15)
cpy := make([]int,15)
fmt.Println(arr)
for i := 0; i < 15; i++ {
arr[i] = i
}
fmt.Println(len(arr))
fmt.Println(arr)
//全部复制
copy(cpy,arr)
fmt.Println(len(cpy))
fmt.Println(cpy)
//指定范围复制
rge := make([]int,15)
copy(rge[3:8],arr[3:8])
fmt.Println(len(rge))
fmt.Println(rge)
}

删除切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
//定义一个切片
arr := make([]int,15)
for i := 0; i < 15; i++ {
arr[i] = i
}
fmt.Printf("原始数据 arr=%v\n",arr)
//头部删除
arr = arr[3:]
fmt.Printf("头部删除 arr=%v\n",arr)
//尾部删除
arr = arr[:10]
fmt.Printf("尾部删除 arr=%v\n",arr)
//中间删除
arr = append(arr[:4],arr[6:]...)
fmt.Printf("中间删除 arr=%v\n",arr)
}

Map

1
var mapname map[keytype]valuetype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
var m map[string]string
m = map[string]string{}
for i := 97; i < 110; i++ {
m["name"+string(i)] = string(i)
}
fmt.Println(len(m))
fmt.Println(m)
//make
m2 :=make(map[string]string)
m3 :=make(map[string]string,100)
for i := 97; i < 110; i++ {
m2["name"+string(i)] = string(i)
}
fmt.Println(len(m2))
fmt.Println(m2)
fmt.Println(m3)
//遍历

for k, v := range m {
fmt.Println(k,v)
}
}

判断是否存在

1
2
3
4
5
6
7
8
9
10
func main() {
m := make(map[string]string)
m["one"] = "one"
fmt.Println(m)
if _,ok := m["one"];ok {
fmt.Println("have")
} else {
fmt.Println("no have")
}
}

删除

1
2
delete(map, 键)

并发map sync.Map

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var cm sync.Map
cm.Store("a", 100)
cm.Store(200, "100")
fmt.Println(cm)
fmt.Println(cm.Load("a"))
cm.Delete(200)
fmt.Println(cm)
cm.Range(func(key, value any) bool {
fmt.Println(key,value)
return true
})
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
list := list.New()
fmt.Println(list)
list.PushFront("hello")
list.PushFront("Hi")
fmt.Println(list.Len())
fmt.Println(list)
fmt.Println(list.Front())
//遍历
for i := list.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
}

排序

golang 的sort包下提供一些排序的api提供给开发者使用

1.简单排序

1
2
3
4
5
6
func main() {
arr:=[]string{"a","d","f","b","z","K","e"}
fmt.Println(arr)
sort.Strings(arr)
fmt.Println(arr)
}

2.自定义排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Interface interface {
// Len is the number of elements in the collection.
Len() int

// Less reports whether the element with index i
// must sort before the element with index j.
//
// If both Less(i, j) and Less(j, i) are false,
// then the elements at index i and j are considered equal.
// Sort may place equal elements in any order in the final result,
// while Stable preserves the original input order of equal elements.
//
// Less must describe a transitive ordering:
// - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
// - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
//
// Note that floating-point comparison (the < operator on float32 or float64 values)
// is not a transitive ordering when not-a-number (NaN) values are involved.
// See Float64Slice.Less for a correct implementation for floating-point values.
Less(i, j int) bool

// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}

案例

在golang中的所有源文件都必须有所归属的包

概念

Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。

定义

1
package pacakgeName

只有在main包下的源文件才能独立运行

导包

1
2
3
4
5
6
7
8
9
10
//单行导入
import "包 1 的路径"
import "包 2 的路径"
//多行导入
import (
"包 1 的路径"
"包 2 的路径"
)
//匿名导入
import _ "fmt"

工具

go mod

1
go mod tidy

govendor

已废弃

地址:github.com/kardianos/govendor

常用

1) fmt

fmt 包实现了格式化的标准输入输出,这与C语言中的 printf 和 scanf 类似。其中的 fmt.Printf() 和 fmt.Println() 是开发者使用最为频繁的函数。

格式化短语派生于C语言,一些短语(%- 序列)是这样使用:

  • %v:默认格式的值。当打印结构时,加号(%+v)会增加字段名;
  • %#v:Go样式的值表达;
  • %T:带有类型的 Go 样式的值表达。
1
2
3
// 接受命令行输入, 不做任何事情
var input string
fmt.Scanln(&input)

2) io

这个包提供了原始的 I/O 操作界面。它主要的任务是对 os 包这样的原始的 I/O 进行封装,增加一些其他相关,使其具有抽象功能用在公共的接口上。

3) bufio

bufio 包通过对 io 包的封装,提供了数据缓冲功能,能够一定程度减少大块数据读写带来的开销。

在 bufio 各个组件内部都维护了一个缓冲区,数据读写操作都直接通过缓存区进行。当发起一次读写操作时,会首先尝试从缓冲区获取数据,只有当缓冲区没有数据时,才会从数据源获取数据更新缓冲。

4) sort

sort 包提供了用于对切片和用户定义的集合进行排序的功能。

5) strconv

strconv 包提供了将字符串转换成基本数据类型,或者从基本数据类型转换为字符串的功能。

6) os

os 包提供了不依赖平台的操作系统函数接口,设计像 Unix 风格,但错误处理是 go 风格,当 os 包使用时,如果失败后返回错误类型而不是错误数量。

7) sync

sync 包实现多线程中锁机制以及其他同步互斥机制。

8) flag

flag 包提供命令行参数的规则定义和传入参数解析的功能。绝大部分的命令行程序都需要用到这个包。

9) encoding/json

JSON 目前广泛用做网络程序中的通信格式。encoding/json 包提供了对 JSON 的基本支持,比如从一个对象序列化为 JSON 字符串,或者从 JSON 字符串反序列化出一个具体的对象等。

10) html/template

主要实现了 web 开发中生成 html 的 template 的一些函数。

11) net/http

net/http 包提供 HTTP 相关服务,主要包括 http 请求、响应和 URL 的解析,以及基本的 http 客户端和扩展的 http 服务。

通过 net/http 包,只需要数行代码,即可实现一个爬虫或者一个 Web 服务器,这在传统语言中是无法想象的。

12) reflect

reflect 包实现了运行时反射,允许程序通过抽象类型操作对象。通常用于处理静态类型 interface{} 的值,并且通过 Typeof 解析出其动态类型信息,通常会返回一个有接口类型 Type 的对象。

13) os/exec

os/exec 包提供了执行自定义 linux 命令的相关实现。

14) strings

strings 包主要是处理字符串的一些函数集合,包括合并、查找、分割、比较、后缀检查、索引、大小写处理等等。

strings 包与 bytes 包的函数接口功能基本一致。

15) regexp

regexp包为正则表达式提供了官方支持,其采用 RE2 语法,除了\c、\C外,Go语言和 Perl、Python 等语言的正则基本一致。

16) bytes

bytes 包提供了对字节切片进行读写操作的一系列函数。字节切片处理的函数比较多,分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。

17) log

log 包主要用于在程序中输出日志。

log 包中提供了三类日志输出接口,Print、Fatal 和 Panic。

  • Print 是普通输出;
  • Fatal 是在执行完 Print 后,执行 os.Exit(1);
  • Panic 是在执行完 Print 后调用 panic() 方法。

18) time

time包为时间相关api包

1
2
3
4
5
6
7
8
9
10
11
func main() {
now := time.Now() //获取当前时间
fmt.Printf("current time:%v\n", now)
year := now.Year() //年
month := now.Month() //月
day := now.Day() //日
hour := now.Hour() //小时
minute := now.Minute() //分钟
second := now.Second() //秒
fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

19) context

接口

定义

1
2
3
4
5
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}

实例

1
2
3
4
type Animal interface {
sleep(t int) bool
walk(d int) bool
}

实现

1
2
3
func (实现类) 方法名(参数) 返回类型 {
//方法体
}

案例

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
package main

import "fmt"
//定义接口
type Animal interface {
sleep(t int) bool
walk(d int) bool
}

type Dog struct {
Name string
Age int
}
//为Dog类实现接口
func (d Dog) sleep(t int) bool {
fmt.Println("the dog sleep time = ",t)
return true
}

func (d Dog) walk(t int) bool {
fmt.Println("the dog walk time = ",t)
return true
}

func main() {
var a1 Animal
a1 = Dog{
Name: "labuladuo",
Age: 1,
}
a1.sleep(1)
a1.walk(1)
fmt.Println(a1)
}

类型转换

Go语言中使用接口断言(type assertions)将接口转换成另外一个接口,也可以将接口转换为另外的类型

1
2
t := i.(T)
t,ok := i.(T)

其中,i 代表接口变量,T 代表转换的目标类型,t 代表转换后的变量。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Car interface {
driver() bool
}
type Animal interface {
sleep(t int) bool
walk(d int) bool
}
type Dog struct {
Name string
Age int
}
func main() {
var dog Animal
dog=Dog{Name: "labuladuo"}
t,ok := dog.(Car)
if ok {
fmt.Println("转换成功",t)
} else {
fmt.Println("类型不匹配")
}
}

关于接口的实现

  • 如果定义的是 (Type)Method,则该类型会隐式的声明一个 (*Type)Method;
  • 如果定义的是 (*Type)Method ,则不会隐式什么一个 (Type)Method。

如果接收者是指针类型,在函数内修改接收者是直接修改原始的变量的,如果是结构体类型因为传递的值拷贝,修改不会影响原始对象。

并发

golang

协程

goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务,它是Go语言并发设计的核心。

开启协程

1
2
3
go func(){
//....
}()

案例

Channel

Channel 是 Golang 在语言级别提供的 goroutine 之间的通信方式,可以使用 channel 在两个或多个 goroutine 之间传递消息。Channel 是进程内的通信方式,因此通过 channel 传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。使用通道发送和接收所需的共享资源,可以在 goroutine 之间消除竞争条件。
当一个资源需要在 goroutine 之间共享时,channel 在 goroutine 之间架起了一个管道,并提供了确保同步交换数据的机制。Channel 是类型相关的,也就是说,一个 channel 只能传递一种类型的值,这个类型需要在声明 channel 时指定。可以通过 channel 共享内置类型、命名类型、结构类型和引用类型的值或者指针。

结构

image.png

定义

1
2
3
4
5
6
7
8
9
10
11
var ChannelName chan ElementType
ChannelName = make(chan ElementType,size)
//或者
ChannelName := make(chan ElementType,size)
//发送数据
ChannelName<-ElementType
//获取数据
ret:=<-ChannelName
//单通道channel
var 通道实例 chan<- 元素类型 // 只能写入数据的通道
var 通道实例 <-chan 元素类型 // 只能读取数据的通道

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
fmt.Println(context.Background())
ch := make(chan int,3)
go func() {
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(1*time.Second)
}
}()
go func() {
for true {
v:=<-ch
fmt.Println(v)
time.Sleep(1*time.Second)
}
}()
time.Sleep(10*time.Second)
}

操作

关闭channel

1
2
3
4
5
6
7
8
func main() {
ch := make(chan int)
close(ch)
_,ok := <-ch
if !ok {
fmt.Println("channel closed")
}
}

buffered channel

Go语言中有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

注意:如果在main线程中直接对无缓冲区chan操作时,会产生deadlock

1
通道实例 := make(chan 通道类型, 缓冲大小)

案例

1
ch := make(chan int,100)

unbuffered channel

无缓冲区是指在接收前没有能力保存任何值的通道。这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。

1
通道实例 := make(chan 通道类型)

遍历chan

1
2
3
4
5
6
7

go func() {
ch := make(chan int, 3)
for {
fmt.Println(<-ch)
}
}()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
go func() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
cnt := 1
for v := range ch {
fmt.Println(v)
cnt++
if cnt == 3 {
close(ch)
}
}
}()

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
type hchan struct {
qcount uint // 当前队列中剩余元素个数
dataqsiz uint // 环形队列长度,即可以存放的元素个数
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 每个元素的大小
closed uint32 // 标识关闭状态
elemtype *_type // 元素类型
sendx uint // 队列下标,指示元素写入时存放到队列中的位置
recvx uint // 队列下标,指示元素从队列的该位置读出
recvq waitq // 等待读消息的goroutine队列
sendq waitq // 等待写消息的goroutine队列
lock mutex // 互斥锁,chan不允许并发读写
}

Context

又称上下文,golang 的 Context 包,是专门用来简化多个goroutine 之间传递数据、超时时间(deadline)、取消信号(cancellation signals)或者其他请求相关的信息

比如有一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的goroutine。这样的话, 我们就可以通过Context,来跟踪这些goroutine,并且通过Context来控制他们的目的

1
2
3
4
5
6
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
  • Deadline方法是获取设置的截止时间的意思,第一个返回式是截止时间,到了这个时间点,Context会自动发起取消请求;第二个返回值ok==false时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。
  • Done方法返回一个只读的chan,类型为struct{},我们在goroutine中,如果该方法返回的chan可以读取,则意味着parent context已经发起了取消请求,我们通过Done方法收到这个信号后,就应该做清理操作,然后退出goroutine,释放资源。之后,Err 方法会返回一个错误,告知为什么 Context 被取消。
  • Err方法返回取消的错误原因,因为什么Context被取消。
  • Value方法获取该Context上绑定的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。

通过根Context派生出子Context

1
2
3
4
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)//返回一个字和函数该函数用于触发ctx的Done函数
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val any) Context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func contextCancel() {
ctx := context.Background()
childCtx, CancelFunc := context.WithCancel(ctx)
go func(ctx context.Context) {
for {
select {
case <-childCtx.Done():
fmt.Println("触发Done")
return
//case xxx:
// do something
}
}
}(childCtx)
time.Sleep(time.Second)
fmt.Println("主函数运行")
//调用
CancelFunc()
fmt.Println("主函数完成")
time.Sleep(5 * time.Second)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func contextDeadline() {
ctx := context.Background()
childCtx, CancelFunc := context.WithDeadline(ctx, time.Now().Add(3*time.Second))
go func(ctx context.Context) {
for {
select {
case <-childCtx.Done():
fmt.Println("触发Done")
return
}
}
}(childCtx)
time.Sleep(time.Second)
fmt.Println("主函数运行")
//也可以手动方式调用,不使用手动方式的话那就按照我们配置的时间
CancelFunc()
fmt.Println("主函数完成")
time.Sleep(5*time.Second)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func contextTimeout() {
ctx := context.Background()
childCtx, _ := context.WithTimeout(ctx, 3*time.Second)
go func(ctx context.Context) {
for {
select {
case <-childCtx.Done():
fmt.Println("触发Done")
return
}
}
}(childCtx)
time.Sleep(time.Second)
fmt.Println("主函数运行")
//手动方式调用,不使用手动方式的话那就按照我们配置的时间
//CancelFunc()
fmt.Println("主函数完成")
time.Sleep(5 * time.Second)
}
1
2
3
4
5
6
7
8
9
10
func contextValue() {
ctx := context.Background()
childCtx := context.WithValue(ctx, "param", "Hello world")
go func(ctx context.Context) {
fmt.Println(ctx.Value("param"))
}(childCtx)
time.Sleep(time.Second)
fmt.Println("主函数完成")
time.Sleep(5 * time.Second)
}

Select

select是go提供类似io多路复用的功能,有的类似Switch语句

特点

  • 监听的case中,没有满足条件的就阻塞
  • 多个满足条件的就任选一个执行
  • select本身不带循环,需要外层的for
  • default通常不用,会产生忙轮询
  • break只能跳出select中的一个case
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //select基本用法
    select {
    case <- chan1:
    // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
    // 如果成功向chan2写入数据,则进行该case处理语句
    default:
    // 如果上面都没有成功,则进入default处理流程
    }
    简单案例
    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
    func main9() {
    ch := make(chan int)
    go func() {
    for {
    select {
    case v := <-ch:
    fmt.Println("向ch读取数据:", v)
    case ch <- 0:
    fmt.Println("向ch写入数据")
    }
    }
    }()
    go func() {
    for i := 0; i < 10; i++ {
    ch <- i
    fmt.Println("W")
    time.Sleep(time.Second * 2)
    }
    }()
    go func() {
    for i := 0; i < 10; i++ {
    <-ch
    time.Sleep(time.Second * 2)
    }
    }()
    time.Sleep(100 * time.Second)
    }
    应用场景

锁 Lock

sync包下提供多种锁支持

Mutex锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
var cnt int
var lock sync.Mutex
for i := 0; i < 10; i++ {
idx:=i
go func() {
lock.Lock()//加锁
cnt++
fmt.Println("协程:", idx, "修改cnt=", cnt)
time.Sleep(time.Second * 1)
lock.Unlock()//释放锁
}()
}
time.Sleep(time.Second * 10)
}

RWMutex锁

读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
var cnt int
rwl:=sync.RWMutex{}
for i := 0; i < 10; i++ {
idx:=i
go func() {
rwl.Lock()//加锁
cnt++
fmt.Println("协程:", idx, "修改cnt=", cnt)
time.Sleep(time.Second * 1)
rwl.Unlock()//释放锁
}()
go func() {
rwl.RLock()//加锁
cnt++
fmt.Println("协程:", idx, "读取cnt=", cnt)
time.Sleep(time.Second * 1)
rwl.RUnlock()//释放锁
}()
}
time.Sleep(time.Second * 10)
}

WaitGroup

类似于阻塞队列

1
2
3
group:=sync.WaitGroup{}
group.Add(1)//添加一个任务
group.Wait()//等待所有任务完成

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
group:=sync.WaitGroup{}
for i := 0; i < 10; i++ {
group.Add(1)//添加一个任务
idx:=i
go func() {
defer func() {
fmt.Println("done",idx)
group.Done()//完成一个任务
}()
time.Sleep(time.Second)
}()
}
group.Wait()//等待所有任务完成
}

GOMAXPROCS

调整并发的运行性能

1
runtime.GOMAXPROCS(逻辑CPU数量)

查询

1
2
3
4

func main() {
fmt.Println(runtime.GOMAXPROCS(runtime.NumCPU()))
}

反射

反射用于获取程序运行时的一些动态数据,解决golang是静态语言缺失动态性的问题,golang的反射相关api位于 reflect 包下

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())//类型和种类
//
dog:=Animal{
name: "labuladuo",
}
typeOf:=reflect.TypeOf(dog)
fmt.Println(typeOf.Name(),typeOf.Kind())
field := "name"
name,ok:=typeOf.FieldByName(field)
if ok {
fmt.Println(name)
fmt.Println(name.Type)
} else {
log.Fatal("could not found field",field)
}
}

I/O

golang的bufio 包中,实现了对数据 I/O 接口的缓冲功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
"bufio"
"bytes"
"fmt"
)

func main() {
data := []byte("Hi golang")
rd := bytes.NewReader(data)
r := bufio.NewReader(rd)
var buf [128]byte
n, err := r.Read(buf[:])
fmt.Println(string(buf[:n]), n, err)
}

文件

Go语言的 os 包下有一个 OpenFile 函数,其原型如下所示:

1
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

其中 name 是文件的文件名,如果不是在当前路径下运行需要加上具体路径;flag 是文件的处理参数,为 int 类型,根据系统的不同具体值可能有所不同,但是作用是相同的。

下面列举了一些常用的 flag 文件处理参数:

  • O_RDONLY:只读模式打开文件;
  • O_WRONLY:只写模式打开文件;
  • O_RDWR:读写模式打开文件;
  • O_APPEND:写操作时将数据附加到文件尾部(追加);
  • O_CREATE:如果不存在将创建一个新文件;
  • O_EXCL:和 O_CREATE 配合使用,文件必须不存在,否则返回一个错误;
  • O_SYNC:当进行一系列写操作时,每次都要等待上次的 I/O 操作完成再进行;
  • O_TRUNC:如果可能,在打开时清空文件。

文本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
////创建文件
//file,err:=os.Create("a.txt")
//if err!=nil {
// log.Fatal("创建失败",err)
//}
//file.Write([]byte("hello world"))
//读取文件
file,err := os.Open("./a.txt")
if err != nil {
fmt.Printf("文件打开失败 [Err:%s]\n", err.Error())
return
}
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n') //读到一个换行就结束
fmt.Println(str)
if err == io.EOF { //io.EOF 表示文件的末尾
break
}
}
fmt.Println("文件读取结束...")
}

二进制操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
info := "hello world"
file, err := os.Create("./file.gob")
if err != nil {
fmt.Println("文件创建失败", err.Error())
return
}
encoder := gob.NewEncoder(file)
err = encoder.Encode(info)
if err != nil {
fmt.Println("编码错误", err.Error())
return
} else {
fmt.Println("编码成功")
}
}

压缩包操作

测试

image.png

单元测试

业务类

1
2
3
4
5
func Hello(name string) int {
// Return a greeting that embeds the name in a message.
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}

测试类(注意类名:_test)

1
2
3
4
5
6
7
8
9
func TestHello(t *testing.T) {
// Return a greeting that embeds the name in a message.
ret:=Hello("mikey")
if ret != 0 {
t.Error("测试失败")
}
fmt.Println(ret)
}

性能测试

1
2
3
func GetArea(x,y int) int {
return x * y
}
1
2
3
4
5
func BenchmarkGetArea(t *testing.B) {
for i := 0; i < t.N; i++ {
GetArea(40, 50)
}
}
1
go test -bench="."

响应结果

1
2
3
4
5
6
7
biaoyang@biaodeMacBook-Pro test % go test -bench="."
goos: darwin
goarch: arm64
pkg: example/hello/test
BenchmarkGetArea-8 1000000000 0.4092 ns/op
PASS
ok example/hello/test 0.934s

覆盖率测试

测试覆盖率是指当运行测试用例时,代码(类,包,模块)中有多少被执行到。覆盖率通常用百分比来表示。例如当我们说一个包的覆盖率是85%的时候,就是说测试用例让包中85%的代码都运行过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "testing"

func BenchmarkGetArea(t *testing.B) {
for i := 0; i < t.N; i++ {
GetArea(40, 50)
}
}

func TestGetArea(t *testing.T) {
ret:=GetArea(10,10)
if ret != 100 {
t.Fail()
}
}
func TestGetAreaError(t *testing.T) {
ret:=GetArea(10,10)
if ret != 10 {
t.Fail()
}
}
1
go test -cover

响应结果

1
2
3
4
5
6
biaoyang@biaodeMacBook-Pro test % go test -cover
--- FAIL: TestGetAreaError (0.00s)
FAIL
coverage: 100.0% of statements
exit status 1
FAIL example/hello/test 0.091s

帮助文档

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
biaoyang@biaodeMacBook-Pro ~ % go help test
usage: go test [build/test flags] [packages] [build/test flags & test binary flags]

'Go test' automates testing the packages named by the import paths.
It prints a summary of the test results in the format:

ok archive/tar 0.011s
FAIL archive/zip 0.022s
ok compress/gzip 0.033s
...

followed by detailed output for each failed package.

'Go test' recompiles each package along with any files with names matching
the file pattern "*_test.go".
These additional files can contain test functions, benchmark functions, fuzz
tests and example functions. See 'go help testfunc' for more.
Each listed package causes the execution of a separate test binary.
Files whose names begin with "_" (including "_test.go") or "." are ignored.

Test files that declare a package with the suffix "_test" will be compiled as a
separate package, and then linked and run with the main test binary.

The go tool will ignore a directory named "testdata", making it available
to hold ancillary data needed by the tests.

As part of building a test binary, go test runs go vet on the package
and its test source files to identify significant problems. If go vet
finds any problems, go test reports those and does not run the test
binary. Only a high-confidence subset of the default go vet checks are
used. That subset is: 'atomic', 'bool', 'buildtags', 'errorsas',
'ifaceassert', 'nilfunc', 'printf', and 'stringintconv'. You can see
the documentation for these and other vet tests via "go doc cmd/vet".
To disable the running of go vet, use the -vet=off flag. To run all
checks, use the -vet=all flag.

All test output and summary lines are printed to the go command's
standard output, even if the test printed them to its own standard
error. (The go command's standard error is reserved for printing
errors building the tests.)

Go test runs in two different modes:

The first, called local directory mode, occurs when go test is
invoked with no package arguments (for example, 'go test' or 'go
test -v'). In this mode, go test compiles the package sources and
tests found in the current directory and then runs the resulting
test binary. In this mode, caching (discussed below) is disabled.
After the package test finishes, go test prints a summary line
showing the test status ('ok' or 'FAIL'), package name, and elapsed
time.

The second, called package list mode, occurs when go test is invoked
with explicit package arguments (for example 'go test math', 'go
test ./...', and even 'go test .'). In this mode, go test compiles
and tests each of the packages listed on the command line. If a
package test passes, go test prints only the final 'ok' summary
line. If a package test fails, go test prints the full test output.
If invoked with the -bench or -v flag, go test prints the full
output even for passing package tests, in order to display the
requested benchmark results or verbose logging. After the package
tests for all of the listed packages finish, and their output is
printed, go test prints a final 'FAIL' status if any package test
has failed.

In package list mode only, go test caches successful package test
results to avoid unnecessary repeated running of tests. When the
result of a test can be recovered from the cache, go test will
redisplay the previous output instead of running the test binary
again. When this happens, go test prints '(cached)' in place of the
elapsed time in the summary line.

The rule for a match in the cache is that the run involves the same
test binary and the flags on the command line come entirely from a
restricted set of 'cacheable' test flags, defined as -benchtime, -cpu,
-list, -parallel, -run, -short, -timeout, -failfast, and -v.
If a run of go test has any test or non-test flags outside this set,
the result is not cached. To disable test caching, use any test flag
or argument other than the cacheable flags. The idiomatic way to disable
test caching explicitly is to use -count=1. Tests that open files within
the package's source root (usually $GOPATH) or that consult environment
variables only match future runs in which the files and environment
variables are unchanged. A cached test result is treated as executing
in no time at all, so a successful package test result will be cached and
reused regardless of -timeout setting.

In addition to the build flags, the flags handled by 'go test' itself are:

-args
Pass the remainder of the command line (everything after -args)
to the test binary, uninterpreted and unchanged.
Because this flag consumes the remainder of the command line,
the package list (if present) must appear before this flag.

-c
Compile the test binary to pkg.test but do not run it
(where pkg is the last element of the package's import path).
The file name can be changed with the -o flag.

-exec xprog
Run the test binary using xprog. The behavior is the same as
in 'go run'. See 'go help run' for details.

-i
Install packages that are dependencies of the test.
Do not run the test.
The -i flag is deprecated. Compiled packages are cached automatically.

-json
Convert test output to JSON suitable for automated processing.
See 'go doc test2json' for the encoding details.

-o file
Compile the test binary to the named file.
The test still runs (unless -c or -i is specified).

The test binary also accepts flags that control execution of the test; these
flags are also accessible by 'go test'. See 'go help testflag' for details.

For more about build flags, see 'go help build'.
For more about specifying packages, see 'go help packages'.

See also: go build, go vet.

断言

在Go语言中类型断言的语法格式如下:

1
value, ok := x.(T)

其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。

1
2
3
4
5
6
func main() {
var x interface{}
x = 10
value, ok := x.(int)
fmt.Print(value, ",", ok)
}

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

type Animal interface {
walk(dist int)int
}

type Dog struct {
}


func (d Dog) walk(dist int)int{
fmt.Println("walk dist",dist)
return dist - 1
}

func main() {
var x interface{}
x = Dog{}
value, ok := x.(Animal)//断言
fmt.Print(value, ",", ok)
}

打桩

打桩是一种在单元测试中替换外部依赖关系的技术。它允许您测试代码而不必使用实际的依赖项,例如数据库、网络服务等。
打桩通常是模拟依赖项的行为,例如返回预定义的数据或抛出特定的错误。这可以帮助测试代码的正确性,并且还可以使测试更快、更可靠。
使用打桩的好处是:

  • 可以使用预定义的数据和错误进行测试,而不必使用真实的依赖项。
  • 可以在测试中更好地控制依赖项的行为,以验证代码的正确性。
  • 可以提高测试的速度和可靠性。
    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
    package test

    import (
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"
    "testing"
    )

    func TestGet(t *testing.T) {
    // 设置打桩
    http.DefaultClient.Transport = &stubTransport{}

    resp, err := http.Get("https://www.baidu.com")
    if err != nil {
    t.Fatal(err)
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
    t.Fatal(err)
    }

    fmt.Println(string(body))
    }

    // 打桩实现
    type stubTransport struct{}

    func (t *stubTransport) RoundTrip(req *http.Request) (*http.Response, error) {
    return &http.Response{
    StatusCode: 200,
    Body: ioutil.NopCloser(strings.NewReader("Hello, world!")),
    }, nil
    }

    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
    package test

    import (
    "fmt"
    "testing"
    )

    type stubDB struct{}

    func (db *stubDB) Get(key string) (string, error) {
    return "value", nil
    }

    func GetData(db *stubDB, key string) (string, error) {
    return db.Get(key)
    }

    func TestGetData(t *testing.T) {
    // 设置打桩
    db := &stubDB{}

    data, err := GetData(db, "key")
    if err != nil {
    t.Fatal(err)
    }
    fmt.Println(data)
    }

Mock

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
package test

import (
"fmt"
"net/http"
"testing"
)

func fetchData(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()

return "data", nil
}

type mockHTTPClient struct{}

func (m *mockHTTPClient) RoundTrip(r *http.Request) (*http.Response, error) {
if r.URL.String() == "https://example.com/data" {
return &http.Response{
StatusCode: http.StatusOK,
}, nil
}
return nil, fmt.Errorf("Invalid URL")
}

func TestFetchData(t *testing.T) {
// Replace the actual http.Get with our mockHTTPClient
http.DefaultClient = &http.Client{
Transport: &mockHTTPClient{},
}

data, err := fetchData("https://example.com/data")
if err != nil {
t.Errorf("fetchData returned error: %v", err)
}
if data != "data" {
t.Errorf("fetchData returned incorrect data: %s", data)
}
}

相关mock库:https://github.com/golang/mock

Mock 和打桩在测试中是指用来替代实际对象的虚拟对象,用于在测试环境中达到测试目的。

  • 打桩(stubbing)是指创建一个模拟对象,该对象返回固定的数据,这些数据在测试过程中是固定不变的。打桩主要用于模拟依赖项,在测试代码中为这些依赖项提供一组固定的数据,以保证测试代码在独立于其依赖项的情况下正常工作。

  • 模拟(mocking)是指创建一个模拟对象,该对象可以根据测试需要提供不同的数据,以验证测试代码的正确性。模拟的目的是测试代码的行为,它允许您验证测试代码是否正确地使用了它的依赖项。

因此,打桩和模拟是测试中不同的技术,但它们都用于代替实际对象,以帮助开发人员在测试环境中测试代码。

日志

垃圾

垃圾回收

  • Go1.3采用标记清除法。
  • Go1.5采用三色标记法。
  • Go1.8采用三色标记法+混合写屏障。

根对象:根对象是指赋值器不需要通过其他对象就可以直接访问到的对象,通过Root对象, 可以追踪到其他存活的对象。常见的root对象有:

  • 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
  • 执行栈:每个 goroutine (包括main函数)都拥有自己的执行栈,这些执行栈上包含栈上的变量及堆内存指针。(堆内存指针即在gorouine中申请或者引用了在堆内存的变量)

v1.3 标记清除法

分为两个阶段:标记和清除
标记阶段:从根对象出发寻找并标记所有存活的对象。
清除阶段:遍历堆中的对象,回收未标记的对象,并加入空闲链表。

缺点是整个过程都需要暂停程序STW。

因为如果不进行STW的话,会出现已经被标记的对象A,引用了新的未被标记的对象B,但由于对象A已经标记过了,不会再重新扫描A对B的可达性,从而将B对象当做垃圾回收掉。(白色对象引用黑色对象的情况

v1.5 三色标记法

将对象标记为白色,灰色或黑色。

  • 白色:不确定对象(默认色);
  • 黑色:存活对象。
  • 灰色:存活对象,子对象待处理。

标记开始时,先将所有对象加入白色集合(需要STW)。首先将根对象标记为灰色,然后将一个对象从灰色集合取出,遍历其子对象,放入灰色集合。同时将取出的对象放入黑色集合,直到灰色集合为空。最后的白色集合对象就是需要清理的对象。

这种方法有一个缺陷,如果对象的引用被用户修改了,那么之前的标记就无效了。因此Go采用了写屏障技术,当对象新增或者更新会将其着色为灰色。

上述的三色标记仍然需要stop the world,如果不STW在标记过程中,如对象6引用了对象5,由于对象6已经扫描过,不会再次扫描,此时对象5仍会被GC掉。

在三色标记法的过程中对象丢失,需要同时满足下面两个条件:

  • 条件一:白色对象被黑色对象引用
  • 条件二:灰色对象与白色对象之间的可达关系遭到破坏

看来只要把上面两个条件破坏掉一个,就可以保证对象不丢失,所以golang团队就提出了两种破坏条件的方式:强三色不变式弱三色不变式

强三色不变式

规则:不允许黑色对象引用白色对象
原理:如果一个黑色对象不直接引用白色对象,那么就不会出现白色对象扫描不到,从而被当做垃圾回收掉的情况。

弱三色不变式

规则:黑色对象可以引用白色对象,但是白色对象的上游必须存在灰色对象
原理: 如果一个白色对象的上游有灰色对象,则这个白色对象一定可以扫描到,从而不被回收。

基于上述两种不变式提到的原则,分别提出了两种实现机制:插入写屏障和删除写屏障

插入写屏障

规则:当一个对象引用另外一个对象时,将另外一个对象标记为灰色。
满足:强三色不变式。不会存在黑色对象引用白色对象

插入写屏障只会发生在堆区,栈区的对象分配操作十分频繁,如果进行写屏障会有性能问题

由于栈上的对像没有插入写机制,在扫描完成后,仍然可能存在栈上的白色对象被黑色对象引用,所以在最后需要对栈上的空间进行STW,防止对象误删除。

写屏障最大的弊端就是,在一次正常的三色标记流程结束后,需要对栈上重新进行一次stw,然后再rescan一次。

删除写屏障

规则:在删除引用时,如果被删除引用的对象自身为灰色或者白色,那么被标记为灰色。满足弱三色不变式。灰色对象到白色对象的路径不会断
解释:白色对象始终会被灰色对象保护

删除写屏障的缺点是当我们对一个对象取消引用后,其没有任何引用也会存活到下一次GC,可能会堆积很多垃圾

1.5采用了插入写屏障

v1.8 三色标记法+混合写屏障

基于上述两种的写屏障的机制进行优化,取长补短,1.8采用了混合写屏障的方式来进行标记

  • GC刚开始的时候,会将栈上的可达对象全部标记为黑色

  • GC期间,任何在栈上新创建的对象,均为黑色

    上面两点只有一个目的,将栈上的可达对象全部标黑,最后无需对栈进行STW,就可以保证栈上的对象不会丢失。有人说,一直是黑色的对象,那么不就永远清除不掉了么,这里强调一下,标记为黑色的是可达对象,不可达的对象一直会是白色,直到最后被回收。

  • 堆上被删除的对象标记为灰色

  • 堆上新添加的对象标记为灰色

混合写屏障,其屏障限制只在堆内存中生效,无需二次扫描栈,提升了GC效率

源码分析

1.隐式调用

go会通过独立的进程扫描已经不再使用的变量,进程gc

2.显式调用

1
runtime.GC()

3.SetFinalizer

在对象被收集前会调用该对象配置的SetFinalizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type Person struct {
Name string
Address string
}


func main() {
p:=Person{
Name: "Leo",
Address: "182121221",
}
fmt.Println(p)
f:=func(p *Person) {
fmt.Println("调用p2设置的SetFinalizer方法:",p)
}
runtime.SetFinalizer(&p, f)
runtime.GC()
time.Sleep(3000)
}

Golang中垃圾回收支持三种模式:
(1)gcBackgroundMode,默认模式,标记与清扫过程都是并发执行的;
(2)gcForceMode,只在清扫阶段支持并发;
(3)gcForceBlockMode,GC全程需要STW。

1
2
3
4
5
6
7
8
// gcMode indicates how concurrent a GC cycle should be.
type gcMode int

const (
gcBackgroundMode gcMode = iota // concurrent GC and sweep
gcForceMode // stop-the-world GC now, concurrent sweep
gcForceBlockMode // stop-the-world GC now and STW sweep (forced by user)
)

1.执行GC函数

src/runtime/mgc.go

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
// GC runs a garbage collection and blocks the caller until the
// garbage collection is complete. It may also block the entire
// program.
func GC() {
// We consider a cycle to be: sweep termination, mark, mark
// termination, and sweep. This function shouldn't return
// until a full cycle has been completed, from beginning to
// end. Hence, we always want to finish up the current cycle
// and start a new one. That means:
//
// 1. In sweep termination, mark, or mark termination of cycle
// N, wait until mark termination N completes and transitions
// to sweep N.
//
// 2. In sweep N, help with sweep N.
//
// At this point we can begin a full cycle N+1.
//
// 3. Trigger cycle N+1 by starting sweep termination N+1.
//
// 4. Wait for mark termination N+1 to complete.
//
// 5. Help with sweep N+1 until it's done.
//
// This all has to be written to deal with the fact that the
// GC may move ahead on its own. For example, when we block
// until mark termination N, we may wake up in cycle N+2.

// Wait until the current sweep termination, mark, and mark
// termination complete.
n := atomic.Load(&work.cycles)
//gc标记
gcWaitOnMark(n)

// We're now in sweep N or later. Trigger GC cycle N+1, which
// will first finish sweep N if necessary and then enter sweep
// termination N+1.
// 开始收集
gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})

// Wait for mark termination N+1 to complete.
gcWaitOnMark(n + 1)

// Finish sweep N+1 before returning. We do this both to
// complete the cycle and because runtime.GC() is often used
// as part of tests and benchmarks to get the system into a
// relatively stable and isolated state.
for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {
sweep.nbgsweep++
Gosched()
}

// Callers may assume that the heap profile reflects the
// just-completed cycle when this returns (historically this
// happened because this was a STW GC), but right now the
// profile still reflects mark termination N, not N+1.
//
// As soon as all of the sweep frees from cycle N+1 are done,
// we can go ahead and publish the heap profile.
//
// First, wait for sweeping to finish. (We know there are no
// more spans on the sweep queue, but we may be concurrently
// sweeping spans, so we have to wait.)
for atomic.Load(&work.cycles) == n+1 && !isSweepDone() {
Gosched()
}

// Now we're really done with sweeping, so we can publish the
// stable heap profile. Only do this if we haven't already hit
// another mark termination.
mp := acquirem()
cycle := atomic.Load(&work.cycles)
if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {
mProf_PostSweep()
}
releasem(mp)
}

2.垃圾标记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// gcWaitOnMark blocks until GC finishes the Nth mark phase. If GC has
// already completed this mark phase, it returns immediately.
func gcWaitOnMark(n uint32) {
for {
// Disable phase transitions.
lock(&work.sweepWaiters.lock)
nMarks := atomic.Load(&work.cycles)
if gcphase != _GCmark {
// We've already completed this cycle's mark.
nMarks++
}
if nMarks > n {
// We're done.
unlock(&work.sweepWaiters.lock)
return
}
// Wait until sweep termination, mark, and mark
// termination of cycle N complete.
work.sweepWaiters.list.push(getg())
goparkunlock(&work.sweepWaiters.lock, waitReasonWaitForGCCycle, traceEvGoBlock, 1)
}
}

3.执行垃圾收集

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// gcStart starts the GC. It transitions from _GCoff to _GCmark (if
// debug.gcstoptheworld == 0) or performs all of GC (if
// debug.gcstoptheworld != 0).
//
// This may return without performing this transition in some cases,
// such as when called on a system stack or with locks held.
func gcStart(trigger gcTrigger) {
// Since this is called from malloc and malloc is called in
// the guts of a number of libraries that might be holding
// locks, don't attempt to start GC in non-preemptible or
// potentially unstable situations.
mp := acquirem()
if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
releasem(mp)
return
}
releasem(mp)
mp = nil

// Pick up the remaining unswept/not being swept spans concurrently
//
// This shouldn't happen if we're being invoked in background
// mode since proportional sweep should have just finished
// sweeping everything, but rounding errors, etc, may leave a
// few spans unswept. In forced mode, this is necessary since
// GC can be forced at any point in the sweeping cycle.
//
// We check the transition condition continuously here in case
// this G gets delayed in to the next GC cycle.
for trigger.test() && sweepone() != ^uintptr(0) {
sweep.nbgsweep++
}

// Perform GC initialization and the sweep termination
// transition.
semacquire(&work.startSema)
// Re-check transition condition under transition lock.
if !trigger.test() {
semrelease(&work.startSema)
return
}

// For stats, check if this GC was forced by the user.
work.userForced = trigger.kind == gcTriggerCycle

// In gcstoptheworld debug mode, upgrade the mode accordingly.
// We do this after re-checking the transition condition so
// that multiple goroutines that detect the heap trigger don't
// start multiple STW GCs.
mode := gcBackgroundMode
if debug.gcstoptheworld == 1 {
mode = gcForceMode
} else if debug.gcstoptheworld == 2 {
mode = gcForceBlockMode
}

// Ok, we're doing it! Stop everybody else
semacquire(&gcsema)
semacquire(&worldsema)

if trace.enabled {
traceGCStart()
}

// Check that all Ps have finished deferred mcache flushes.
for _, p := range allp {
if fg := atomic.Load(&p.mcache.flushGen); fg != mheap_.sweepgen {
println("runtime: p", p.id, "flushGen", fg, "!= sweepgen", mheap_.sweepgen)
throw("p mcache not flushed")
}
}

gcBgMarkStartWorkers()

systemstack(gcResetMarkState)

work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
if work.stwprocs > ncpu {
// This is used to compute CPU time of the STW phases,
// so it can't be more than ncpu, even if GOMAXPROCS is.
work.stwprocs = ncpu
}
work.heap0 = atomic.Load64(&gcController.heapLive)
work.pauseNS = 0
work.mode = mode

now := nanotime()
work.tSweepTerm = now
work.pauseStart = now
if trace.enabled {
traceGCSTWStart(1)
}
systemstack(stopTheWorldWithSema)
// Finish sweep before we start concurrent scan.
systemstack(func() {
finishsweep_m()
})

// clearpools before we start the GC. If we wait they memory will not be
// reclaimed until the next GC cycle.
clearpools()

work.cycles++

// Assists and workers can start the moment we start
// the world.
gcController.startCycle(now, int(gomaxprocs), trigger)

// Notify the CPU limiter that assists may begin.
gcCPULimiter.startGCTransition(true, now)

// In STW mode, disable scheduling of user Gs. This may also
// disable scheduling of this goroutine, so it may block as
// soon as we start the world again.
if mode != gcBackgroundMode {
schedEnableUser(false)
}

// Enter concurrent mark phase and enable
// write barriers.
//
// Because the world is stopped, all Ps will
// observe that write barriers are enabled by
// the time we start the world and begin
// scanning.
//
// Write barriers must be enabled before assists are
// enabled because they must be enabled before
// any non-leaf heap objects are marked. Since
// allocations are blocked until assists can
// happen, we want enable assists as early as
// possible.
setGCPhase(_GCmark)

gcBgMarkPrepare() // Must happen before assist enable.
gcMarkRootPrepare()

// Mark all active tinyalloc blocks. Since we're
// allocating from these, they need to be black like
// other allocations. The alternative is to blacken
// the tiny block on every allocation from it, which
// would slow down the tiny allocator.
gcMarkTinyAllocs()

// At this point all Ps have enabled the write
// barrier, thus maintaining the no white to
// black invariant. Enable mutator assists to
// put back-pressure on fast allocating
// mutators.
atomic.Store(&gcBlackenEnabled, 1)

// In STW mode, we could block the instant systemstack
// returns, so make sure we're not preemptible.
mp = acquirem()

// Concurrent mark.
systemstack(func() {
now = startTheWorldWithSema(trace.enabled)
work.pauseNS += now - work.pauseStart
work.tMark = now
memstats.gcPauseDist.record(now - work.pauseStart)

// Release the CPU limiter.
gcCPULimiter.finishGCTransition(now)
})

// Release the world sema before Gosched() in STW mode
// because we will need to reacquire it later but before
// this goroutine becomes runnable again, and we could
// self-deadlock otherwise.
semrelease(&worldsema)
releasem(mp)

// Make sure we block instead of returning to user code
// in STW mode.
if mode != gcBackgroundMode {
Gosched()
}

semrelease(&work.startSema)
}

内存

Go语言的内存管理是基于TCMalloc (Thread-Caching Malloc) 模型设计的,TCMalloc是一种典型的分级、多链表内存管理模型,可以很好的应对碎片化内存。

内存组成组件

go实现了自己的内存分配器,维护一个大的全局内存,每个线程(Golang中为P)维护一个小的私有内存,私有内存不足再从全局申请。为了方便自主管理内存,做法便是先向系统申请一块内存,然后将内存切割成小块,通过一定的内存分配算法管理内存。以64位系统为例,Golang程序启动时会向系统申请的内存如下图所示:

预申请的内存划分为spans、bitmap、arena三部分。其中arena即为所谓的堆区,应用中需要的内存从这里分配。其中spans和bitmap是为了管理arena区而存在的。

  • arena的大小为512G,为了方便管理把arena区域划分成一个个的page,每个page为8KB,一共有512GB/8KB个页;
  • spans区域存放span的指针,每个指针对应一个page,所以span区域的大小为(512GB/8KB)*指针大小8byte = 512M
  • bitmap区域大小也是通过arena计算出来,主要用于GC。

arean

spans

该内存区域主要存放内存管理单元mspan指针,每个指针对于1或N个page,512MB = 512GB/8KB(页大小)*8B(指针大小)

为了解决内存碎片化带了的分配问题(查找空闲内存时间复杂度增加),golang中划分出67种内存块(span),8b-32k范围,组成空闲链表,提高分配效率

  • class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
  • bytes /obj:该class代表对象的字节数
  • bytes/span:每个span占用堆的字节数,也即页数*页大小
  • objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)
  • waste bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)
    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
    // class  bytes/obj  bytes/span  objects  tail waste  max waste  min align
    // 1 8 8192 1024 0 87.50% 8
    // 2 16 8192 512 0 43.75% 16
    // 3 24 8192 341 8 29.24% 8
    // 4 32 8192 256 0 21.88% 32
    // 5 48 8192 170 32 31.52% 16
    // 6 64 8192 128 0 23.44% 64
    // 7 80 8192 102 32 19.07% 16
    // 8 96 8192 85 32 15.95% 32
    // 9 112 8192 73 16 13.56% 16
    // 10 128 8192 64 0 11.72% 128
    // 11 144 8192 56 128 11.82% 16
    // 12 160 8192 51 32 9.73% 32
    // 13 176 8192 46 96 9.59% 16
    // 14 192 8192 42 128 9.25% 64
    // 15 208 8192 39 80 8.12% 16
    // 16 224 8192 36 128 8.15% 32
    // 17 240 8192 34 32 6.62% 16
    // 18 256 8192 32 0 5.86% 256
    // 19 288 8192 28 128 12.16% 32
    // 20 320 8192 25 192 11.80% 64
    // 21 352 8192 23 96 9.88% 32
    // 22 384 8192 21 128 9.51% 128
    // 23 416 8192 19 288 10.71% 32
    // 24 448 8192 18 128 8.37% 64
    // 25 480 8192 17 32 6.82% 32
    // 26 512 8192 16 0 6.05% 512
    // 27 576 8192 14 128 12.33% 64
    // 28 640 8192 12 512 15.48% 128
    // 29 704 8192 11 448 13.93% 64
    // 30 768 8192 10 512 13.94% 256
    // 31 896 8192 9 128 15.52% 128
    // 32 1024 8192 8 0 12.40% 1024
    // 33 1152 8192 7 128 12.41% 128
    // 34 1280 8192 6 512 15.55% 256
    // 35 1408 16384 11 896 14.00% 128
    // 36 1536 8192 5 512 14.00% 512
    // 37 1792 16384 9 256 15.57% 256
    // 38 2048 8192 4 0 12.45% 2048
    // 39 2304 16384 7 256 12.46% 256
    // 40 2688 8192 3 128 15.59% 128
    // 41 3072 24576 8 0 12.47% 1024
    // 42 3200 16384 5 384 6.22% 128
    // 43 3456 24576 7 384 8.83% 128
    // 44 4096 8192 2 0 15.60% 4096
    // 45 4864 24576 5 256 16.65% 256
    // 46 5376 16384 3 256 10.92% 256
    // 47 6144 24576 4 0 12.48% 2048
    // 48 6528 32768 5 128 6.23% 128
    // 49 6784 40960 6 256 4.36% 128
    // 50 6912 49152 7 768 3.37% 256
    // 51 8192 8192 1 0 15.61% 8192
    // 52 9472 57344 6 512 14.28% 256
    // 53 9728 49152 5 512 3.64% 512
    // 54 10240 40960 4 0 4.99% 2048
    // 55 10880 32768 3 128 6.24% 128
    // 56 12288 24576 2 0 11.45% 4096
    // 57 13568 40960 3 256 9.99% 256
    // 58 14336 57344 4 0 5.35% 2048
    // 59 16384 16384 1 0 12.49% 8192
    // 60 18432 73728 4 0 11.11% 2048
    // 61 19072 57344 3 128 3.57% 128
    // 62 20480 40960 2 0 6.87% 4096
    // 63 21760 65536 3 256 6.25% 256
    // 64 24576 24576 1 0 11.45% 8192
    // 65 27264 81920 3 128 10.00% 128
    // 66 28672 57344 2 0 4.91% 4096
    // 67 32768 32768 1 0 12.50% 8192

    // alignment bits min obj size
    // 8 3 8
    // 16 4 32
    // 32 5 256
    // 64 6 512
    // 128 7 768
    // 4096 12 28672
    // 8192 13 32768

    const (
    _MaxSmallSize = 32768
    smallSizeDiv = 8
    smallSizeMax = 1024
    largeSizeDiv = 128
    _NumSizeClasses = 68
    _PageShift = 13
    )

    var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
    var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}
    var class_to_divmagic = [_NumSizeClasses]uint32{0, ^uint32(0)/8 + 1, ^uint32(0)/16 + 1, ^uint32(0)/24 + 1, ^uint32(0)/32 + 1, ^uint32(0)/48 + 1, ^uint32(0)/64 + 1, ^uint32(0)/80 + 1, ^uint32(0)/96 + 1, ^uint32(0)/112 + 1, ^uint32(0)/128 + 1, ^uint32(0)/144 + 1, ^uint32(0)/160 + 1, ^uint32(0)/176 + 1, ^uint32(0)/192 + 1, ^uint32(0)/208 + 1, ^uint32(0)/224 + 1, ^uint32(0)/240 + 1, ^uint32(0)/256 + 1, ^uint32(0)/288 + 1, ^uint32(0)/320 + 1, ^uint32(0)/352 + 1, ^uint32(0)/384 + 1, ^uint32(0)/416 + 1, ^uint32(0)/448 + 1, ^uint32(0)/480 + 1, ^uint32(0)/512 + 1, ^uint32(0)/576 + 1, ^uint32(0)/640 + 1, ^uint32(0)/704 + 1, ^uint32(0)/768 + 1, ^uint32(0)/896 + 1, ^uint32(0)/1024 + 1, ^uint32(0)/1152 + 1, ^uint32(0)/1280 + 1, ^uint32(0)/1408 + 1, ^uint32(0)/1536 + 1, ^uint32(0)/1792 + 1, ^uint32(0)/2048 + 1, ^uint32(0)/2304 + 1, ^uint32(0)/2688 + 1, ^uint32(0)/3072 + 1, ^uint32(0)/3200 + 1, ^uint32(0)/3456 + 1, ^uint32(0)/4096 + 1, ^uint32(0)/4864 + 1, ^uint32(0)/5376 + 1, ^uint32(0)/6144 + 1, ^uint32(0)/6528 + 1, ^uint32(0)/6784 + 1, ^uint32(0)/6912 + 1, ^uint32(0)/8192 + 1, ^uint32(0)/9472 + 1, ^uint32(0)/9728 + 1, ^uint32(0)/10240 + 1, ^uint32(0)/10880 + 1, ^uint32(0)/12288 + 1, ^uint32(0)/13568 + 1, ^uint32(0)/14336 + 1, ^uint32(0)/16384 + 1, ^uint32(0)/18432 + 1, ^uint32(0)/19072 + 1, ^uint32(0)/20480 + 1, ^uint32(0)/21760 + 1, ^uint32(0)/24576 + 1, ^uint32(0)/27264 + 1, ^uint32(0)/28672 + 1, ^uint32(0)/32768 + 1}
    var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
    var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 41, 42, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 49, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67}

bitmap

  • 每个字节都会表示 arena 区域中的 32 字节是否空闲
  • 主要用于gc
  • 16G = 512GB/32B*1B

内存管理组件

go的内存管理组件主要有:mspan、mcache、mcentral和mheap、heapArena

  • mspan:为内存管理的基础单元,直接存储数据的地方。
  • mcache:每个运行期的goroutine都会绑定的一个mcache(具体来讲是绑定的GMP并发模型中的P,所以可以无锁分配mspan),mcache会分配goroutine运行中所需要的内存空间(即mspan)。
  • mcentral:为所有mcache切分好后备的mspan
  • mheap:代表Go程序持有的所有堆空间。还会管理闲置的span,需要时向操作系统申请新内存。

mcache

在GPM关系中,会在每个 P 下都有一个 mcache 字段,用来表示内存信息。在 Go 1.2 版本以前调度器使用的是 GM 模型,将 mcache 放在了 M 里,但发现存在诸多问题,其中对于内存这一块存在着巨大的浪费。每个 M 都持有 mcache 和 stack alloc,但只有在 M 运行 Go 代码时才需要使用内存(每个 mcache 可以高达2mb),当 M 在处于 syscall 或 网络请求 的时候是不需要内存的,再加上 M 又是允许创建多个的,这就造成了内存的很大浪费。所以从go 1.3版本开始使用了GPM模型,这样在高并发状态下,每个G只有在运行的时候才会使用到内存,而每个 G 会绑定一个P,所以它们在运行只占用一份 mcache,对于 mcache 的数量就是P 的数量,同时并发访问时也不会产生锁。

对于 GM 模型除了上面提供到内存浪费的问题,还有其它问题,如单一全局锁sched.Lock、goroutine 传递问题和内存局部性等。在 P 中,一个 mcache 除了可以用来缓存小对象外,还包含一些本地分配统计信息。由于在每个P下面都存在一个mcache ,所以多个 goroutine 并发请求内存时是无锁的。

在mcache中默认会有alloc = 136个span空间提供给当前P使用

源码

src/runtime/mheap.go

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
// Per-thread (in Go, per-P) cache for small objects.
// This includes a small object cache and local allocation stats.
// No locking needed because it is per-thread (per-P).
//
// mcaches are allocated from non-GC'd memory, so any heap pointers
// must be specially handled.
//
//go:notinheap
type mcache struct {
// The following members are accessed on every malloc,
// so they are grouped here for better caching.
nextSample uintptr // trigger heap sample after allocating this many bytes
scanAlloc uintptr // bytes of scannable heap allocated

// Allocator cache for tiny objects w/o pointers.
// See "Tiny allocator" comment in malloc.go.

// tiny points to the beginning of the current tiny block, or
// nil if there is no current tiny block.
//
// tiny is a heap pointer. Since mcache is in non-GC'd memory,
// we handle it by clearing it in releaseAll during mark
// termination.
//
// tinyAllocs is the number of tiny allocations performed
// by the P that owns this mcache.
// 微对象分配器
tiny uintptr//tiny对象基地址
tinyoffset uintptr//下一个空闲内存所在偏移量
tinyAllocs uintptr//tiny对象分配个数

// The rest is not accessed on every malloc.
// numSpanClasses = 68 << 1 = 136
// 小对象分配器
alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
//按class分组的mspan列表

stackcache [_NumStackOrders]stackfreelist
//栈缓存

// flushGen indicates the sweepgen during which this mcache
// was last flushed. If flushGen != mheap_.sweepgen, the spans
// in this mcache are stale and need to the flushed so they
// can be swept. This is done in acquirep.
flushGen uint32
}

mspan

双向链表结构

mspan 是分配内存时的基本单元。当分配内存时,会在mcache中查找适合规格的可用 mspan,此时不需要加锁,因此分配效率极高。

如上面提到的,Go将内存块分为大小不同的 67 种,然后再把这 67 种大内存块,逐个分为小块(可以近似理解为大小不同的相当于page)称之为span(连续的page)

源码

src/runtime/mheap.go

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
//go:notinheap
type mspan struct {
//下一个span
next *mspan // next span in list, or nil if none
prev *mspan // previous span in list, or nil if none
list *mSpanList // For debugging. TODO: Remove.
//指向链表的指针

startAddr uintptr // address of first byte of span aka s.base()
//开始地址
npages uintptr // number of pages in span
//span中的页数(一个span 是由多个page组成的)

manualFreeList gclinkptr // list of free objects in mSpanManual spans

// freeindex is the slot index between 0 and nelems at which to begin scanning
// for the next free object in this span.
// Each allocation scans allocBits starting at freeindex until it encounters a 0
// indicating a free object. freeindex is then adjusted so that subsequent scans begin
// just past the newly discovered free object.
//
// If freeindex == nelem, this span has no free objects.
//
// allocBits is a bitmap of objects in this span.
// If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
// then object n is free;
// otherwise, object n is allocated. Bits starting at nelem are
// undefined and should never be referenced.
//
// Object n starts at address n*elemsize + (start << pageShift).
freeindex uintptr
// TODO: Look up nelems from sizeclass and remove this field if it
// helps performance.
nelems uintptr // number of object in the span.

// Cache of the allocBits at freeindex. allocCache is shifted
// such that the lowest bit corresponds to the bit freeindex.
// allocCache holds the complement of allocBits, thus allowing
// ctz (count trailing zero) to use it directly.
// allocCache may contain bits beyond s.nelems; the caller must ignore
// these.
allocCache uint64

// allocBits and gcmarkBits hold pointers to a span's mark and
// allocation bits. The pointers are 8 byte aligned.
// There are three arenas where this data is held.
// free: Dirty arenas that are no longer accessed
// and can be reused.
// next: Holds information to be used in the next GC cycle.
// current: Information being used during this GC cycle.
// previous: Information being used during the last GC cycle.
// A new GC cycle starts with the call to finishsweep_m.
// finishsweep_m moves the previous arena to the free arena,
// the current arena to the previous arena, and
// the next arena to the current arena.
// The next arena is populated as the spans request
// memory to hold gcmarkBits for the next GC cycle as well
// as allocBits for newly allocated spans.
//
// The pointer arithmetic is done "by hand" instead of using
// arrays to avoid bounds checks along critical performance
// paths.
// The sweep will free the old allocBits and set allocBits to the
// gcmarkBits. The gcmarkBits are replaced with a fresh zeroed
// out memory.
allocBits *gcBits
gcmarkBits *gcBits// gc 三色位图

// sweep generation:
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen, the span is swept and ready to use
// if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
// if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
// h->sweepgen is incremented by 2 after every GC

sweepgen uint32
divMul uint32 // for divide by elemsize
allocCount uint16 // number of allocated objects
spanclass spanClass // size class and noscan (uint8)
state mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods)
needzero uint8 // needs to be zeroed before allocation
allocCountBeforeCache uint16 // a copy of allocCount that is stored just before this span is cached
elemsize uintptr // computed from sizeclass or from npages
limit uintptr // end of data in span
speciallock mutex // guards specials list
specials *special // linked list of special records sorted by offset.

// freeIndexForScan is like freeindex, except that freeindex is
// used by the allocator whereas freeIndexForScan is used by the
// GC scanner. They are two fields so that the GC sees the object
// is allocated only when the object and the heap bits are
// initialized (see also the assignment of freeIndexForScan in
// mallocgc, and issue 54596).
freeIndexForScan uintptr
}

mcentral

两个包含/不包含空闲对象的mspan列表,mspan列表多线程共享,使用互斥锁保护

源码

src/runtime/mspanset.go

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
// Central list of free objects of a given size.
//
//go:notinheap
type mcentral struct {
spanclass spanClass

// partial and full contain two mspan sets: one of swept in-use
// spans, and one of unswept in-use spans. These two trade
// roles on each GC cycle. The unswept set is drained either by
// allocation or by the background sweeper in every GC cycle,
// so only two roles are necessary.
//
// sweepgen is increased by 2 on each GC cycle, so the swept
// spans are in partial[sweepgen/2%2] and the unswept spans are in
// partial[1-sweepgen/2%2]. Sweeping pops spans from the
// unswept set and pushes spans that are still in-use on the
// swept set. Likewise, allocating an in-use span pushes it
// on the swept set.
//
// Some parts of the sweeper can sweep arbitrary spans, and hence
// can't remove them from the unswept set, but will add the span
// to the appropriate swept list. As a result, the parts of the
// sweeper and mcentral that do consume from the unswept list may
// encounter swept spans, and these should be ignored.
partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
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
// A spanSet is a set of *mspans.
//
// spanSet is safe for concurrent push and pop operations.
type spanSet struct {
// A spanSet is a two-level data structure consisting of a
// growable spine that points to fixed-sized blocks. The spine
// can be accessed without locks, but adding a block or
// growing it requires taking the spine lock.
//
// Because each mspan covers at least 8K of heap and takes at
// most 8 bytes in the spanSet, the growth of the spine is
// quite limited.
//
// The spine and all blocks are allocated off-heap, which
// allows this to be used in the memory manager and avoids the
// need for write barriers on all of these. spanSetBlocks are
// managed in a pool, though never freed back to the operating
// system. We never release spine memory because there could be
// concurrent lock-free access and we're likely to reuse it
// anyway. (In principle, we could do this during STW.)

spineLock mutex
spine unsafe.Pointer // *[N]*spanSetBlock, accessed atomically
spineLen uintptr // Spine array length, accessed atomically
spineCap uintptr // Spine array cap, accessed under lock

// index is the head and tail of the spanSet in a single field.
// The head and the tail both represent an index into the logical
// concatenation of all blocks, with the head always behind or
// equal to the tail (indicating an empty set). This field is
// always accessed atomically.
//
// The head and the tail are only 32 bits wide, which means we
// can only support up to 2^32 pushes before a reset. If every
// span in the heap were stored in this set, and each span were
// the minimum size (1 runtime page, 8 KiB), then roughly the
// smallest heap which would be unrepresentable is 32 TiB in size.
index headTailIndex
}

mheap

mheap即是我们说的堆区

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Main malloc heap.
// The heap itself is the "free" and "scav" treaps,
// but all the other global data is here too.
//
// mheap must not be heap-allocated because it contains mSpanLists,
// which must not be heap-allocated.
//
//go:notinheap
type mheap struct {
// lock must only be acquired on the system stack, otherwise a g
// could self-deadlock if its stack grows with the lock held.
lock mutex

_ uint32 // 8-byte align pages so its alignment is consistent with tests.

pages pageAlloc // page allocation data structure

sweepgen uint32 // sweep generation, see comment in mspan; written during STW

// allspans is a slice of all mspans ever created. Each mspan
// appears exactly once.
//
// The memory for allspans is manually managed and can be
// reallocated and move as the heap grows.
//
// In general, allspans is protected by mheap_.lock, which
// prevents concurrent access as well as freeing the backing
// store. Accesses during STW might not hold the lock, but
// must ensure that allocation cannot happen around the
// access (since that may free the backing store).
allspans []*mspan // all spans out there

// _ uint32 // align uint64 fields on 32-bit for atomics

// Proportional sweep
//
// These parameters represent a linear function from gcController.heapLive
// to page sweep count. The proportional sweep system works to
// stay in the black by keeping the current page sweep count
// above this line at the current gcController.heapLive.
//
// The line has slope sweepPagesPerByte and passes through a
// basis point at (sweepHeapLiveBasis, pagesSweptBasis). At
// any given time, the system is at (gcController.heapLive,
// pagesSwept) in this space.
//
// It is important that the line pass through a point we
// control rather than simply starting at a 0,0 origin
// because that lets us adjust sweep pacing at any time while
// accounting for current progress. If we could only adjust
// the slope, it would create a discontinuity in debt if any
// progress has already been made.
pagesInUse atomic.Uint64 // pages of spans in stats mSpanInUse
pagesSwept atomic.Uint64 // pages swept this cycle
pagesSweptBasis atomic.Uint64 // pagesSwept to use as the origin of the sweep ratio
sweepHeapLiveBasis uint64 // value of gcController.heapLive to use as the origin of sweep ratio; written with lock, read without
sweepPagesPerByte float64 // proportional sweep ratio; written with lock, read without
// TODO(austin): pagesInUse should be a uintptr, but the 386
// compiler can't 8-byte align fields.

// Page reclaimer state

// reclaimIndex is the page index in allArenas of next page to
// reclaim. Specifically, it refers to page (i %
// pagesPerArena) of arena allArenas[i / pagesPerArena].
//
// If this is >= 1<<63, the page reclaimer is done scanning
// the page marks.
reclaimIndex atomic.Uint64

// reclaimCredit is spare credit for extra pages swept. Since
// the page reclaimer works in large chunks, it may reclaim
// more than requested. Any spare pages released go to this
// credit pool.
reclaimCredit atomic.Uintptr

// arenas is the heap arena map. It points to the metadata for
// the heap for every arena frame of the entire usable virtual
// address space.
//
// Use arenaIndex to compute indexes into this array.
//
// For regions of the address space that are not backed by the
// Go heap, the arena map contains nil.
//
// Modifications are protected by mheap_.lock. Reads can be
// performed without locking; however, a given entry can
// transition from nil to non-nil at any time when the lock
// isn't held. (Entries never transitions back to nil.)
//
// In general, this is a two-level mapping consisting of an L1
// map and possibly many L2 maps. This saves space when there
// are a huge number of arena frames. However, on many
// platforms (even 64-bit), arenaL1Bits is 0, making this
// effectively a single-level map. In this case, arenas[0]
// will never be nil.
arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena

// heapArenaAlloc is pre-reserved space for allocating heapArena
// objects. This is only used on 32-bit, where we pre-reserve
// this space to avoid interleaving it with the heap itself.
heapArenaAlloc linearAlloc

// arenaHints is a list of addresses at which to attempt to
// add more heap arenas. This is initially populated with a
// set of general hint addresses, and grown with the bounds of
// actual heap arena ranges.
arenaHints *arenaHint

// arena is a pre-reserved space for allocating heap arenas
// (the actual arenas). This is only used on 32-bit.
arena linearAlloc

// allArenas is the arenaIndex of every mapped arena. This can
// be used to iterate through the address space.
//
// Access is protected by mheap_.lock. However, since this is
// append-only and old backing arrays are never freed, it is
// safe to acquire mheap_.lock, copy the slice header, and
// then release mheap_.lock.
allArenas []arenaIdx

// sweepArenas is a snapshot of allArenas taken at the
// beginning of the sweep cycle. This can be read safely by
// simply blocking GC (by disabling preemption).
sweepArenas []arenaIdx

// markArenas is a snapshot of allArenas taken at the beginning
// of the mark cycle. Because allArenas is append-only, neither
// this slice nor its contents will change during the mark, so
// it can be read safely.
markArenas []arenaIdx

// curArena is the arena that the heap is currently growing
// into. This should always be physPageSize-aligned.
curArena struct {
base, end uintptr
}

_ uint32 // ensure 64-bit alignment of central

// central free lists for small size classes.
// the padding makes sure that the mcentrals are
// spaced CacheLinePadSize bytes apart, so that each mcentral.lock
// gets its own cache line.
// central is indexed by spanClass.
central [numSpanClasses]struct {
mcentral mcentral
pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
}

spanalloc fixalloc // allocator for span*
cachealloc fixalloc // allocator for mcache*
specialfinalizeralloc fixalloc // allocator for specialfinalizer*
specialprofilealloc fixalloc // allocator for specialprofile*
specialReachableAlloc fixalloc // allocator for specialReachable
speciallock mutex // lock for special record allocators.
arenaHintAlloc fixalloc // allocator for arenaHints

unused *specialfinalizer // never set, just here to force the specialfinalizer type into DWARF
}

heapArena
g1.11版本以及之后引入二维稀疏内存

[l1][l2]heapArena

  • Linux x86-64架构 l1=1 l2=4194304 32MB = 4194304*8B(指针大小) ,每个heapArena可以管理64MB内存
  • 4M*64MB = 256TB内存
    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
    // A heapArena stores metadata for a heap arena. heapArenas are stored
    // outside of the Go heap and accessed via the mheap_.arenas index.
    //
    //go:notinheap
    type heapArena struct {
    // bitmap stores the pointer/scalar bitmap for the words in
    // this arena. See mbitmap.go for a description. Use the
    // heapBits type to access this.
    bitmap [heapArenaBitmapBytes]byte
    // 标记内内存是否使用

    // spans maps from virtual address page ID within this arena to *mspan.
    // For allocated spans, their pages map to the span itself.
    // For free spans, only the lowest and highest pages map to the span itself.
    // Internal pages map to an arbitrary span.
    // For pages that have never been allocated, spans entries are nil.
    //
    // Modifications are protected by mheap.lock. Reads can be
    // performed without locking, but ONLY from indexes that are
    // known to contain in-use or stack spans. This means there
    // must not be a safe-point between establishing that an
    // address is live and looking it up in the spans array.
    spans [pagesPerArena]*mspan
    //对应的mspan管理器

    // pageInUse is a bitmap that indicates which spans are in
    // state mSpanInUse. This bitmap is indexed by page number,
    // but only the bit corresponding to the first page in each
    // span is used.
    //
    // Reads and writes are atomic.
    pageInUse [pagesPerArena / 8]uint8

    // pageMarks is a bitmap that indicates which spans have any
    // marked objects on them. Like pageInUse, only the bit
    // corresponding to the first page in each span is used.
    //
    // Writes are done atomically during marking. Reads are
    // non-atomic and lock-free since they only occur during
    // sweeping (and hence never race with writes).
    //
    // This is used to quickly find whole spans that can be freed.
    //
    // TODO(austin): It would be nice if this was uint64 for
    // faster scanning, but we don't have 64-bit atomic bit
    // operations.
    pageMarks [pagesPerArena / 8]uint8

    // pageSpecials is a bitmap that indicates which spans have
    // specials (finalizers or other). Like pageInUse, only the bit
    // corresponding to the first page in each span is used.
    //
    // Writes are done atomically whenever a special is added to
    // a span and whenever the last special is removed from a span.
    // Reads are done atomically to find spans containing specials
    // during marking.
    pageSpecials [pagesPerArena / 8]uint8

    // checkmarks stores the debug.gccheckmark state. It is only
    // used if debug.gccheckmark > 0.
    checkmarks *checkmarksMap

    // zeroedBase marks the first byte of the first page in this
    // arena which hasn't been used yet and is therefore already
    // zero. zeroedBase is relative to the arena base.
    // Increases monotonically until it hits heapArenaBytes.
    //
    // This field is sufficient to determine if an allocation
    // needs to be zeroed because the page allocator follows an
    // address-ordered first-fit policy.
    //
    // Read atomically and written with an atomic CAS.
    zeroedBase uintptr
    //内存管理基地址
    }

内存分配策略

总体过程

1.new,make最祭调用mallocac
2.大于32KB对象,直接从mheap中分配,构成一个span
3.小于16byte且无指针(noscan),使用tiny分配器,合并分配
4.小于16byte有指针或16byte-32KB,如果mcache中有对应class的空闲mspan,则直接从该mspan中分配一个slot.
5. (mcentral.cachespan) mcache没有对应的空余span,则从对应mcentral中申请一个有空余slot的span到mcache中再进行分配
6. ( mcentral.grow)对应mcentral没有空余span,则向(mheap(mheap_alloc)中申请一个span,能sweep出span则返回,否则看mheap的sfree mTreap能否分配最大于该size的连续页,能则分配,多的页放回
7.mheap的free mTreap无可用,则源用sysAlloc(mmap)向系统申请.
8.6,7步中获得的内存构进成span,返回给mcache,分配对象。

源码分析

src/runtime/malloc.go

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if gcphase == _GCmarktermination {
throw("mallocgc called with gcphase == _GCmarktermination")
}

if size == 0 {
return unsafe.Pointer(&zerobase)
}
userSize := size
if asanenabled {
// Refer to ASAN runtime library, the malloc() function allocates extra memory,
// the redzone, around the user requested memory region. And the redzones are marked
// as unaddressable. We perform the same operations in Go to detect the overflows or
// underflows.
size += computeRZlog(size)
}

if debug.malloc {
if debug.sbrk != 0 {
align := uintptr(16)
if typ != nil {
// TODO(austin): This should be just
// align = uintptr(typ.align)
// but that's only 4 on 32-bit platforms,
// even if there's a uint64 field in typ (see #599).
// This causes 64-bit atomic accesses to panic.
// Hence, we use stricter alignment that matches
// the normal allocator better.
if size&7 == 0 {
align = 8
} else if size&3 == 0 {
align = 4
} else if size&1 == 0 {
align = 2
} else {
align = 1
}
}
return persistentalloc(size, align, &memstats.other_sys)
}

if inittrace.active && inittrace.id == getg().goid {
// Init functions are executed sequentially in a single goroutine.
inittrace.allocs += 1
}
}

// assistG is the G to charge for this allocation, or nil if
// GC is not currently active.
var assistG *g
if gcBlackenEnabled != 0 {
// Charge the current user G for this allocation.
assistG = getg()
if assistG.m.curg != nil {
assistG = assistG.m.curg
}
// Charge the allocation against the G. We'll account
// for internal fragmentation at the end of mallocgc.
assistG.gcAssistBytes -= int64(size)

if assistG.gcAssistBytes < 0 {
// This G is in debt. Assist the GC to correct
// this before allocating. This must happen
// before disabling preemption.
gcAssistAlloc(assistG)
}
}

// Set mp.mallocing to keep from being preempted by GC.
mp := acquirem()
if mp.mallocing != 0 {
throw("malloc deadlock")
}
if mp.gsignal == getg() {
throw("malloc during signal")
}
mp.mallocing = 1

shouldhelpgc := false
dataSize := userSize
c := getMCache(mp)
if c == nil {
throw("mallocgc called without a P or outside bootstrapping")
}
var span *mspan
var x unsafe.Pointer
noscan := typ == nil || typ.ptrdata == 0
// In some cases block zeroing can profitably (for latency reduction purposes)
// be delayed till preemption is possible; delayedZeroing tracks that state.
delayedZeroing := false
if size <= maxSmallSize {
if noscan && size < maxTinySize {
// Tiny allocator.
//
// Tiny allocator combines several tiny allocation requests
// into a single memory block. The resulting memory block
// is freed when all subobjects are unreachable. The subobjects
// must be noscan (don't have pointers), this ensures that
// the amount of potentially wasted memory is bounded.
//
// Size of the memory block used for combining (maxTinySize) is tunable.
// Current setting is 16 bytes, which relates to 2x worst case memory
// wastage (when all but one subobjects are unreachable).
// 8 bytes would result in no wastage at all, but provides less
// opportunities for combining.
// 32 bytes provides more opportunities for combining,
// but can lead to 4x worst case wastage.
// The best case winning is 8x regardless of block size.
//
// Objects obtained from tiny allocator must not be freed explicitly.
// So when an object will be freed explicitly, we ensure that
// its size >= maxTinySize.
//
// SetFinalizer has a special case for objects potentially coming
// from tiny allocator, it such case it allows to set finalizers
// for an inner byte of a memory block.
//
// The main targets of tiny allocator are small strings and
// standalone escaping variables. On a json benchmark
// the allocator reduces number of allocations by ~12% and
// reduces heap size by ~20%.
off := c.tinyoffset
// Align tiny pointer for required (conservative) alignment.
if size&7 == 0 {
off = alignUp(off, 8)
} else if goarch.PtrSize == 4 && size == 12 {
// Conservatively align 12-byte objects to 8 bytes on 32-bit
// systems so that objects whose first field is a 64-bit
// value is aligned to 8 bytes and does not cause a fault on
// atomic access. See issue 37262.
// TODO(mknyszek): Remove this workaround if/when issue 36606
// is resolved.
off = alignUp(off, 8)
} else if size&3 == 0 {
off = alignUp(off, 4)
} else if size&1 == 0 {
off = alignUp(off, 2)
}
if off+size <= maxTinySize && c.tiny != 0 {
// The object fits into existing tiny block.
x = unsafe.Pointer(c.tiny + off)
c.tinyoffset = off + size
c.tinyAllocs++
mp.mallocing = 0
releasem(mp)
return x
}
// Allocate a new maxTinySize block.
span = c.alloc[tinySpanClass]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(tinySpanClass)
}
x = unsafe.Pointer(v)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0
// See if we need to replace the existing tiny block with the new one
// based on amount of remaining free space.
if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
// Note: disabled when race detector is on, see comment near end of this function.
c.tiny = uintptr(x)
c.tinyoffset = size
}
size = maxTinySize
} else {
var sizeclass uint8
if size <= smallSizeMax-8 {
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
size = uintptr(class_to_size[sizeclass])
spc := makeSpanClass(sizeclass, noscan)
span = c.alloc[spc]
v := nextFreeFast(span)
if v == 0 {
v, span, shouldhelpgc = c.nextFree(spc)
}
x = unsafe.Pointer(v)
if needzero && span.needzero != 0 {
memclrNoHeapPointers(unsafe.Pointer(v), size)
}
}
} else {
shouldhelpgc = true
// For large allocations, keep track of zeroed state so that
// bulk zeroing can be happen later in a preemptible context.
span = c.allocLarge(size, noscan)
span.freeindex = 1
span.allocCount = 1
size = span.elemsize
x = unsafe.Pointer(span.base())
if needzero && span.needzero != 0 {
if noscan {
delayedZeroing = true
} else {
memclrNoHeapPointers(x, size)
// We've in theory cleared almost the whole span here,
// and could take the extra step of actually clearing
// the whole thing. However, don't. Any GC bits for the
// uncleared parts will be zero, and it's just going to
// be needzero = 1 once freed anyway.
}
}
}

var scanSize uintptr
if !noscan {
heapBitsSetType(uintptr(x), size, dataSize, typ)
if dataSize > typ.size {
// Array allocation. If there are any
// pointers, GC has to scan to the last
// element.
if typ.ptrdata != 0 {
scanSize = dataSize - typ.size + typ.ptrdata
}
} else {
scanSize = typ.ptrdata
}
c.scanAlloc += scanSize
}

// Ensure that the stores above that initialize x to
// type-safe memory and set the heap bits occur before
// the caller can make x observable to the garbage
// collector. Otherwise, on weakly ordered machines,
// the garbage collector could follow a pointer to x,
// but see uninitialized memory or stale heap bits.
publicationBarrier()
// As x and the heap bits are initialized, update
// freeIndexForScan now so x is seen by the GC
// (including convervative scan) as an allocated object.
// While this pointer can't escape into user code as a
// _live_ pointer until we return, conservative scanning
// may find a dead pointer that happens to point into this
// object. Delaying this update until now ensures that
// conservative scanning considers this pointer dead until
// this point.
span.freeIndexForScan = span.freeindex

// Allocate black during GC.
// All slots hold nil so no scanning is needed.
// This may be racing with GC so do it atomically if there can be
// a race marking the bit.
if gcphase != _GCoff {
gcmarknewobject(span, uintptr(x), size, scanSize)
}

if raceenabled {
racemalloc(x, size)
}

if msanenabled {
msanmalloc(x, size)
}

if asanenabled {
// We should only read/write the memory with the size asked by the user.
// The rest of the allocated memory should be poisoned, so that we can report
// errors when accessing poisoned memory.
// The allocated memory is larger than required userSize, it will also include
// redzone and some other padding bytes.
rzBeg := unsafe.Add(x, userSize)
asanpoison(rzBeg, size-userSize)
asanunpoison(x, userSize)
}

if rate := MemProfileRate; rate > 0 {
// Note cache c only valid while m acquired; see #47302
if rate != 1 && size < c.nextSample {
c.nextSample -= size
} else {
profilealloc(mp, x, size)
}
}
mp.mallocing = 0
releasem(mp)

// Pointerfree data can be zeroed late in a context where preemption can occur.
// x will keep the memory alive.
if delayedZeroing {
if !noscan {
throw("delayed zeroing on data that may contain pointers")
}
memclrNoHeapPointersChunked(size, x) // This is a possible preemption point: see #47302
}

if debug.malloc {
if debug.allocfreetrace != 0 {
tracealloc(x, size, typ)
}

if inittrace.active && inittrace.id == getg().goid {
// Init functions are executed sequentially in a single goroutine.
inittrace.bytes += uint64(size)
}
}

if assistG != nil {
// Account for internal fragmentation in the assist
// debt now that we know it.
assistG.gcAssistBytes -= int64(size - dataSize)
}

if shouldhelpgc {
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}

if raceenabled && noscan && dataSize < maxTinySize {
// Pad tinysize allocations so they are aligned with the end
// of the tinyalloc region. This ensures that any arithmetic
// that goes off the top end of the object will be detectable
// by checkptr (issue 38872).
// Note that we disable tinyalloc when raceenabled for this to work.
// TODO: This padding is only performed when the race detector
// is enabled. It would be nice to enable it if any package
// was compiled with checkptr, but there's no easy way to
// detect that (especially at compile time).
// TODO: enable this padding for all allocations, not just
// tinyalloc ones. It's tricky because of pointer maps.
// Maybe just all noscan objects?
x = add(x, size-dataSize)
}

return x
}

工具

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
Go is a tool for managing Go source code.

Usage:

go <command> [arguments]

The commands are:

bug start a bug report
build compile packages and dependencies
clean remove object files and cached files
doc show documentation for package or symbol
env print Go environment information
fix update packages to use new APIs
fmt gofmt (reformat) package sources
generate generate Go files by processing source
get add dependencies to current module and install them
install compile and install packages and dependencies
list list packages or modules
mod module maintenance
work workspace maintenance
run compile and run Go program
test test packages
tool run specified go tool
version print Go version
vet report likely mistakes in packages

Use "go help <command>" for more information about a command.

Additional help topics:

buildconstraint build constraints
buildmode build modes
c calling between Go and C
cache build and test caching
environment environment variables
filetype file types
go.mod the go.mod file
gopath GOPATH environment variable
gopath-get legacy GOPATH go get
goproxy module proxy protocol
importpath import path syntax
modules modules, module versions, and more
module-get module-aware go get
module-auth module authentication using go.sum
packages package lists and patterns
private configuration for downloading non-public code
testflag testing flags
testfunc testing functions
vcs controlling version control with GOVCS

Use "go help <topic>" for more information about that topic.

go build 命令

go clean 命令

go run 命令

go fmt 命令

go install 命令

go get 命令

go generate 命令

go test 命令

go pprof 命令

参考:https://www.yuque.com/aichihongdouheyumi/blog/serwt6h1lv6wlv86

go tool 命令

展示出go相关工具

  1. addr2line
  1. asm
  1. buildid
  1. cgo
  1. compile

  2. cover

  1. dist
  1. doc
  1. fix
  1. link
  1. nm
  1. objdump 反编译工具

    1
    go tool objdump -S main.go
  2. pack

  1. pprof
  1. test2json
  1. trace
  1. vet

案例

创建项目

通过go mod来管理依赖和创建项目

1
go mod init example/godemo

简单输出

1
2
3
4
5
6
7
8
package main//必须是main包下才能执行main方法

import "fmt"

func main() {
fmt.Println("hello")
}

引入依赖

1
go mod tidy

下载失败配置代理

1
2
go env -w GO111MODULE="on"
go env -w GOPROXY=https://goproxy.cn,direct

计算函数运行时间

1
2
3
4
5
6
7
8
9
10
11
12
func test() {
start := time.Now() // 获取当前时间
sum := 0
for i := 0; i < 100000000; i++ {
sum++
}
elapsed := time.Since(start)
fmt.Println("该函数执行完成耗时:", elapsed)
}
func main() {
test()
}

规范

https://github.com/xxjwxc/uber_go_guide_cn

资料

安装:https://go.dev/doc/install
教程:http://c.biancheng.net/golang/
教程:https://pkg.go.dev/


Golang篇-Golang学习笔记
https://mikeygithub.github.io/2019/05/18/yuque/Golang篇-Golang学习笔记/
作者
Mikey
发布于
2019年5月18日
许可协议