go学习记录


疑问

sync.Mutex避免复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
避免结构体中存在sync.Mutex,使用值方法

type Counter struct {
sync.Mutex
n int64
}

// 此方法实现是没问题的。
func (c *Counter) Increase(d int64) (r int64) {
c.Lock()
c.n += d
r = c.n
c.Unlock()
return
}

// Counter值将被复制,不会起到保护的作用,没有意义
func (c Counter) Value() (r int64) {
c.Lock()
r = c.n
c.Unlock()
return c.n
}

数据竞争

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
如何查看数据竞争
go run -race xxx.go

如何判断数据竞争
一个变量在协程中有写操作,并且在其他协程中存在读或写操作,并且没用到Mutex等同步手段,就会引起数据竞争

数据竞争例子
a := 1
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
a = 2
}()

go func() {
defer wg.Done()
a = 3
}()

wg.Wait()
fmt.Println(a)

使用Mutex避免数据竞争
a := 1
wg := sync.WaitGroup{}
wg.Add(2)
sm := sync.Mutex{}
go func() {
defer wg.Done()
defer sm.Unlock()
sm.Lock() // 加锁直到unlock之前,其他协程无法获取到锁,确保了数据不会被其他协程操作
a = 2 // 这边只是简单的例子,实际情况可能做更多的操作
}()

go func() {
defer wg.Done()
defer sm.Unlock()
sm.Lock()
a = 3
}()

wg.Wait()
fmt.Println(a)

指针安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var pT atomic.Pointer[T]
var ta, tb = T{1}, T{2}

pT.Store(&ta) // 设置pT为ta的指针
fmt.Println(pT.Load()) // 读取pT的值
pa1 := pT.Load() // 将pT的值赋值给pa1
fmt.Println(pa1 == &ta) // pa1是ta的指针 结果为true
pa2 := pT.Swap(&tb) // 设置pT的值为tb的指针,并把旧的指针赋值给pa2
fmt.Println(pa2 == &ta) // pa2是旧指针,所以结果为true
fmt.Println(pT.Load()) // tb的指针

b := pT.CompareAndSwap(&ta, &tb) // pT存储tb的指针,不是ta的指针,所以结果是false
fmt.Println(b) // false
b = pT.CompareAndSwap(&tb, &ta) // pt存储tb的指针,与第一个变量一直,替换pt存储为ta的指针,所以结果是true
fmt.Println(b) // true

sync.Cond协程之间的通知

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
cond := sync.NewCond(&sync.Mutex{})
queue := []int{}

go func() {
for {
cond.L.Lock() // 加锁
for len(queue) == 0 { // 直到queue的长度为0时,等待,不适用if是为了防止叫醒之后queue是被清空的状态,引起panic
cond.Wait() // 暂停等待唤醒
}

fmt.Println(queue[0])
queue = queue[1:]

cond.Signal() // 唤醒其他协程
cond.L.Unlock() // 解锁
}
}()

for i := 0; ; i++ {
cond.L.Lock()
queue = append(queue, i)

for len(queue) == 2 { // queue长度到达2就会进入等待,等待协程消化完成后queue<2,进入下一个循环
cond.Wait()
}
cond.Signal()
cond.L.Unlock()
}

读写锁的顺序

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
读锁可以在其他读锁获取的时候同时获取,存在其他读锁则会阻塞
写锁不能在存在读锁或者写锁的时候获取,会阻塞
读锁可已在写锁阻塞的时候,优先获取读锁,哪怕已经有一个写锁阻塞,写锁会继续阻塞
读锁可已在读锁阻塞的时候,如果已经有写锁阻塞,写锁获取锁

var m sync.RWMutex // 这段代码的结果可能为 a b d e c或者a b e d c,其中c因为阻塞被d和e优先获取了写锁
go func() {
m.RLock()
fmt.Println("a", time.Now())
time.Sleep(time.Second)
m.RUnlock()
}() // 0 秒获取锁a 睡1秒 1秒的时候释放锁
go func() {
time.Sleep(time.Second * 1 / 4)
m.Lock()
fmt.Println("b", time.Now())
time.Sleep(time.Second)
m.Unlock()
}() // 0.25 秒想要获锁b 阻塞 1 秒获取到锁b, 2秒释放锁
go func() {
time.Sleep(time.Second * 2 / 4)
m.Lock()
fmt.Println("c", time.Now())
m.Unlock()
}() // 0.5秒 想要获取锁c 阻塞,在读锁解锁完成后才获取到写锁
go func() {
time.Sleep(time.Second * 3 / 4)
m.RLock()
fmt.Println("d", time.Now())
m.RUnlock()
}() // 0.75秒想要获得读锁d 读锁优先级比写锁高,2秒的时候获取到读锁
go func() {
time.Sleep(time.Second)
m.RLock()
fmt.Println("e", time.Now())
m.RUnlock()
}() // 1秒想要获得读锁d 读锁优先级比写锁高,2秒的时候获取到读锁
time.Sleep(time.Second * 3)

恐慌恢复

1
2
3
4
5
6
7
defer func() {
if v := recover(); v != nil {
fmt.Println(v)
}
}()

多个恐慌一起发生将执行最后一个恐慌

延迟调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
defer t.M(1).M(2).M(5).M(6) 会先调用M(1),M(2),M(5),再调用defer M(6)

循环内的延迟调用想在每个循环里提前执行可以放入func里
files := []string{"/xxxx/s.log", "", "", "", ""}
for _, file := range files {
err := func() error {
f, err := os.Open(file)

if nil != err {
return err
}

defer f.Close() //在func里会关闭文件,防止过多临时内存泄露

...各种文件操作

return nil
}()

if nil != err {
fmt.Println(err.Error())
}
}

反射

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
获取指针的基类型
a := 123
b := reflect.ValueOf(&a)
fmt.Println(b.Type(), b.Elem().Type()) // *int int b.Elem()获取指针的基累型
fmt.Println(b.Elem().CanSet()) // true 是否能被赋值修改
b.Elem().Set(reflect.ValueOf(456)) // 赋值,CanSet返回true可以赋值
fmt.Println(a) // 456

结构体中非导出字段不能赋值修改
type T struct {
X int `max:"99" min:"0" default:"0"`
Y, z bool `optional:" yes" Sans:"1"`
}

func main() {
a := T{}
b := reflect.ValueOf(&a)
el := b.Elem() //a和el值相同类型不同

for i := 0; i < el.NumField(); i++ {
fmt.Println(reflect.TypeOf(a).Field(i).Name, el.Field(i).CanSet()) //X true Y true z false
}
}

获取方法和字段
type T struct {
X int `max:"99" min:"0" default:"0"`
Y, Z bool `optional:" yes" Sans:"1"`
}

func (t T) Save() {

}

func main() {
a := T{}
re := reflect.TypeOf(a)
fmt.Println(re.NumMethod(), re.NumField()) //1 3 1个方法,3个字段

//获取所有显式方法名(大写开头的方法)
for i := 0; i < re.NumMethod(); i++ {
fmt.Println(re.Method(i).Name) // Save
}

//获取所有变量名
for i := 0; i < re.NumField(); i++ {
fmt.Println(re.Field(i).Name) // X Y Z
}
}

获取类型
a := 123
fmt.Println(reflect.TypeOf(a)) // int

获取tag值
type T struct {
X int `max:"99" min:"0" default:"0"` //:前后不能有空格,值必须使用双引号,键不能包含冒号和空格
Y, Z bool `optional:" yes" Sans:"1"`
}

a := T{}
re := reflect.TypeOf(a)
fmt.Println(re.Field(0).Tag) // max:"99" min:"0" default:"0"
fmt.Println(re.Field(0).Tag.Get("max")) // 99
fmt.Println(re.Field(0).Tag.Lookup("max")) // 99 true

判断是否实现了接口
type in interface {
Save()
}

type T struct {
X int `max:"99" min:"0" default:"0"`
Y, Z bool `optional:" yes" Sans:"1"`
}

func (t T) Save() {

}

func main() {
a := T{}
re := reflect.TypeOf(a)
fmt.Println(re.Implements(reflect.TypeOf(new(in)).Elem())) //true
//new(in)创建接口指针
//reflect.TypeOf(new(in))获取接口指针的类型
//reflect.TypeOf(new(in)).Elem()获取接口指针类型对应的接口,相当于*new(in)
//re.Implements()判断是否实现了接口in
}

指针重新赋值

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
指针重新赋值会导致指针指向的内存地址发生变化
a := 1
b := &a
fmt.Println(*b, b) // 1 指向a
c := 2
b = &c
fmt.Println(*b, b) // 2 指向c
a = 3
fmt.Println(*b, b) // 2 修改了a,但指针指向c,所以没有发生变化

// 例子
type X int

func (x X) M1() {
fmt.Println(x)
}

func (x *X) M2() {
fmt.Println(*x)
}

type T struct{ X }

type S struct{ *T }

func main() {
var t = &T{X: 1}
var s = S{T: t}
var f = s.M1
var g = s.M2
fmt.Printf("%p", s.T)
fmt.Println()
s.X = 2
f() // 1 M1方法是传值,X的值是副本,不会随着外部修改X而变化
g() // 2 M2方法是传引用,X的值是指针,会根据内存修改而发生变化
s.M1() // 2
s.M2() // 2
fmt.Printf("%p", s.T)
fmt.Println()
s.T = &T{X: 3} // s.T的内存地址发生变化,指向了新的T指针
f() // 1 M1传值不会变化
g() // 2 M2还是旧指针的引用
s.M1() // 3
s.M2() // 3
fmt.Printf("%p", s.T)
fmt.Println()
}

看不懂的语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
遍历函数特性 go 1.23版本
func SquareLessThan50(onKeyValue func(int, int) bool) {
//遍历啊>=0 | <8
for i := range 8 {
if !onKeyValue(i, i*i) {
return
}
}
}

for i, ii := range SquareLessThan50 {
fmt.Printf("%v:%v ", i, ii) //输出0,0 1,1 2,4 .... 7,49
}

动态类型接口值比较

1
2
3
4
5
6
7
8
接口包含动态类型和动态变量,必须两者都相等才相等
var a *string = nil
var b *int = nil
var c, d interface{} = a, b
var i interface{} = nil

c!=d!=i // 动态类型不同,c的动态类型是*string,b的动态类型是*int,i的动态类型是nil
i == nil

断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
接口类型才有断言,可以用来判断值的类型
var a interface{} = 1

//EmptyInterface是个空的接口
//如果没有ok,断言成功正常赋值,失败会照成恐慌panic
if c, ok := a.(EmptyInterface); ok { // 如果断言成功 ok返回true,失败返回false
fmt.Println(c) // c = 1,接口a被断言成空接口
} else {
fmt.Println(c, ok)
}

if c, ok := a.(int); ok { // 如果断言成功 ok返回true,失败返回false
fmt.Println(c) // c = 1,接口a被断言成空接口
} else {
fmt.Println(c, 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
一个接口的方法完全包含另一个接口,接可以转化成另一个接口
空的接口类型可以起到any的作用,定义空接口类型可以转成任何类型的数据

type EmptyInterface interface {
}

var a EmptyInterface
a = 1
fmt.Println(a, reflect.TypeOf(a)) // 1, int
a = "b"
fmt.Println(a, reflect.TypeOf(a)) // b, string

接口的继承
type S interface {
Abort() string
xx() string
}

方法实现了接口中的所有函数就是继承了接口,少一个函数都不行
type AA struct { // AA 继承了接口 S
}

func (aa AA) Abort() string {
return "aa"
}

func (aa AA) xx() string {
return "xx"
}

type BB struct { // BB 没有继承接口 S
}

func (bb BB) Abort() string {
return "bb"
}

func main() {
var a S
a = AA{} // 可以直接定义成接口类型,AA 必须有 Abort和xx两个方法,否则报错
fmt.Println(a.Abort(), reflect.TypeOf(a)) // aa main.AA
b := BB{}
fmt.Println(b.Abort(), reflect.TypeOf(b)) // bb main.BB
a = b // 报错,如果BB存在xx方法就是正确的
}

多态,可以将接口作为方法的变量使用
func test(s S) string {
return s.xx()
}

func main() {
var a S = AA{}
fmt.Println(test(a)) // xx
a = BB{}
fmt.Println(test(a)) // bb
}

运算分析

1
2
3
4
const s = "Go101.org"

var a byte = 1 << len(s) / 128 // a=4,len(s)为常量,1 << len(s) / 128会在编译时被计算
var b byte = 1 << len(s[:]) / 128 // b=0,len(s[:])不为常量, 所以先计算1 << len(s[:]) = 512,512超出byte范围,结果为0

循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
循环中直接修改value不会影响到映射的数据
a := map[string]int{"1": 1, "2": 2, "3": 3}
for key, value := range a {
value++
fmt.Println(value, a[key]) // value!=a[key]
a[key] = value
fmt.Println(value, a[key])
}

大数组可以使用指针或者切片遍历,开销小,或者抛弃value,使用arr[key]的形式

var a = [10000000]int{}
for x, y := range &a { // 或者range a[:]
y++
fmt.Println(y, a[x]) // y!=a[x]
a[x] = y
fmt.Println(y, a[x])
}

var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
fmt.Printf("Sys = %v MiB\n", memStats.Sys/1024/1024) //查看内存开销

不可寻址

1
2
3
4
5
6
可以使用&获取内存地址
结构体struct可寻址,但map中但struct不可寻址,map的值是通过函数调用返回的,地址不稳定,
可直接使用指针,map[string]*[3]int{}
数组array可寻址,&[...]int{1, 2, 3}[1]是临时值,不可寻址,需要现将[...]int{1, 2, 3}赋值才能寻址
切片slice可寻址,&[]int{1, 2, 3}[1]和数组一样是错误的,append扩展切片后地址会发生变化
映射可寻址,但映射的key和value不可寻址 &a["b"]会报错,因为map的值是通过函数调用返回的,地址不稳定,value的值如果是切片无法寻址但可以修改,因为切片是引用类型

切片数组映射

1
2
3
4
5
6
7
8
9
10
11
12
13
结构体struct可以作为映射map的下标key,可以用来表示多对一的查询

切片slice或者数组array不存在下标的情况下,下标=前一个元素的下标+1
第一个元素下标缺失,下标=0
下标必须为非负数整形常量 int>0,uint

映射map和切片slice赋值给另一个映射或切片,内存是共用的
数组array赋值给另一个数组,内存是不共用的

映射map的第二个返回值,第二个返回值为bool,用来表示键在map中是否存在
m := map[string]int{"abc": 123, "xyz": 789}
n, present := m["hello"] //present用来表示hello在m中是否存在,返回true or false,n在hello不存在的是否返回默认值0

类型转换

1
2
3
4
5
6
7
8
9
10
指针类型的转换要把指针类型用()括起来,例如(*int64)()
type MyInt int64
type Ta *int64
type Tb *MyInt

func main() {
var ta Ta
z := Tb((*MyInt)((*int64)(ta))) // 现将ta的类型从Ta转成*int64,在转成*MyInt,在转成Tb
fmt.Println(z)
}

计算

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
不同的数据类型变量不能运算操作
var c int32 = 20
var d int8 = 4
c + d //错误
没有类型的常量能与变量(有类型且类型不同的不行)
const d = 4
c + d //正确,d会隐式转换成int32的类型

(位)异或a ^ b,ab对位相同为0,不同为1
1001 ^ 1100 = 0101

位清a &^ b,等价于 a & (^b)
b位为1,a位清零,b位为0,a位不变
1001 &^ 1100 = 1001 & 0011 = 0001

取模
a % b = 余数,正负值取决于a的正负值,需要ab均为整数
-20 % 6 = -2 // -3 * 6 + -2
-20 % -6 = -2 // 3 * -6 + -2
20 % -6 = 2 // -3 * -6 + 2
20 % 6 = 2 // 3 * 6 + 2

除以0
var a = 1.0
var b = 0.0
fmt.Println(a/b, b/b) // +Inf NaN
fmt.Println(1.0 / 0.0) // 错误

常量计算
1.2 + 3/2 = 2.2
因为3/2是int计算 = 1,加上1.2 = 2.2
1.2 + 3.0/2 = 2.7
1.2 + 3/2.0 = 2.7

计算有先后顺序
3/2*0.1 = 0.1 // 3 / 2 = int 1 * 0.1 = 0.1
0.1*3/2 = 0.15 // 0.1 * 3 = float 0.3 / 2 = 0.15

变量作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
内部声明的同名变量会在内部起作用,不会影响到外部同名的变量的值
var y = 3
var z int

func main() {
x := 1
if true {
x, y := 10, y+1
z = y * 10
fmt.Println(x, y, z) //x=10,y=4,z=40
}
fmt.Println(x, y, z) //x=1,y=3,z=40
}

类型转换

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
常量转换溢出报错
例子:
const c = 254
var x uint8 = c + 1 //正确
var x uint8 = c + 2 //错误,溢出

变量转换溢出转为二进制截断高位重新计算值
int类型包含负数 开头的数字1代表负数,0代表正数,
如果是负数剩下的位数取反(0000001变成1111110)加一,
正式可以直接计算
uint类型不包含负数可以直接计算
例子:
var c int32 = 255
255的二进制是 00000000 00000000 00000000 11111111
var x = int8(c)
int8是2^8,包含正负数,范围-128~127,溢出
转为int8后只保留8位 11111111
int8的计算方法 1开头的数字代表负数,取反加一,0开头的代表正数,直接计算
取反 00000000 = 0,+1=1,1开头的数字代表负数所以结果为-1

常量不设置自身的类型能够隐式转换
例子:
const c = 254
var x uint8 = c + 1 //正确
const c2 int32 = 254
var x uint8 = c2 + 1 //错误

常量设置了自身的类型需要显式转换
例子:
const c int16 = 255
var x uint8 = c //错误
var x uint8 = uint8(c) //正确

变量无法隐式转换
例子:
var c = 255
var x int8 = c //错误

无类型浮点数常量无法直接转换为int类型
例子:
var x int = int(1.1) //错误
var y = 1.1
var x = int(y) //正确

例子2:
const a = 1.1
var x = int(a) //错误
var y = a
var x = int(y) //正确

赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
:=的规则是至少有一个新的变量,_不是新的变量
x, z := 1, 2
z:=2 //错误的,z已经声明过的变量
z, y := 3, 3 //是正确的,声明变量y并且给z赋值
z, x := 2, 1//错误的,没有新声明的变量

_, _ = x, z //正确的
_, _ := x, y //错误的

//在包级变量里是正确的,在局部变量里会报错
var x, y = a + 1, 5
var a, b, c = b + 1, c + 1, y
包级变量,会根据依赖顺序自动排序 y=5,c=y,b=c+1,a=b+1,x=a+1的顺序执行
局部变量是顺序执行,会用到没有初始化的a,b,c变量而报错

一个死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type I interface {
m()
}

type T struct {
I
}

func main() {
var t T
var i = &t
t.I = i
i.m() // ->i是t的指针所以调用t.m()->t.I.m(),t,I = i->i.m() 死循环
}