y以下是我的go学习之路,仅供参考

https://juejin.cn/post/7038967716459315208

go入门

go语法基础

go菜鸟

https://studygolang.com/articles/35591

go底层

Golang深入理解GPM模型

Golang中GC回收机制三色标记与混合写屏障

go修养之路

1、最常用的调试 golang 的 bug 以及性能问题的实践方法? 2、Golang的协程调度器原理及GMP设计思想? 3、Golang中逃逸现象, 变量“何时栈?何时堆?” 4、Golang中make与new有何区别? 5、Golang三色标记+混合写屏障GC模式全分析 6、面向对象的编程思维理解interface 7、Golang中的Defer必掌握的7知识点 8、精通Golang项目依赖Go modules 9、一站式精通Golang内存管理

1、数据定义 2、数组和切片 3、Map 4、interface 5、channel 6、WaitGroup

1、流?I/O操作?阻塞?epoll? 2、分布式从ACID、CAP、BASE的理论推进 3、对于操作系统而言进程、线程以及Goroutine协程的区别 4、Go是否可以无限go? 如何限定数量? 5、单点Server的N种并发模型汇总 6、TCP中TIME_WAIT状态意义详解 7、动态保活Worker工作池设计

接口

image-20220419102939558

image-20220419103352247

image-20220419103920050

类型

image-20220419101101985

image-20220419101351585

image-20220419101624452

断言

空接口.(具体类型)

image-20220419105310780

image-20220419105349984

非空接口.(具体类型)

image-20220419105600100

image-20220419105715940

image-20220419105831277

空接口.(非空接口)

image-20220419110035021

image-20220419110614473

就算能从缓存中查到对应的itab,也要进一步判断itab.fun[0]是否等于0,这是因为断言失败的类型组合其对应的itab结构体也会被缓存起来,只是会把itab.fun[0]置为0,用以表明这里的动态类型并没有实现对应的接口,这样以后在遇到同种类型断言时,就不用再去检查方法列表了,直接断言失败

image-20220419110814519

非空接口.(非空接口)

image-20220419110934644

image-20220419111025591

image-20220419111255642

类型断言的关键是明确接口的动态类型以及对应的类型实现了哪些方法,而明确这些的关键还是类型元数据

method

方法本质上就是函数,只不过在调用时,接收者会作为第一个参数传入

给内置类型定义方法是不被允许的

而接口类型是无效的方法接收者

image-20220418164647600

image-20220418165932868

局部变量a没有被修改

image-20220418170257574

实现了修改

image-20220418170619497

指针调用值的方法,值调用指针的方法

image-20220418173138572

image-20220418173433872

所以,本质是来说,方法表达式和方法变量都是funcval

panic

defer 最大的功能是 panic 后依然有效 所以defer可以保证你的一些资源一定会被关闭,从而避免一些异常出现的问题。

image-20220418133448388

image-20220418145312930

没有recover发生时,panic执行defer的方式,先标记后释放,目的是为了终止之前发生的panic

所有在panic链表上的项都会被输出,顺序与panic发生的顺序一致

recover

移除并跳出当前的panic

image-20220418153747196

image-20220418153703921

image-20220418154209938

defer

1.12

defer函数如果含参,会先把参数确定下来,再进入g._defer中

https://www.kancloud.cn/aceld/golang/1958310

image-20220417205657879

image-20220418104344295

image-20220418104426862

执行时从头开始

image-20220418104608997

image-20220418104939332

image-20220418105130384

image-20220418105412747

go语言会预先分配不同规格的defer池,没有空闲的或没有大小合适的就再进行堆分配

image-20220418105655571

image-20220418110301490

image-20220418110546254

image-20220418111744873

image-20220418110800385

1.13

image-20220418131306308

image-20220418131534971

官方说提升30%

1.14

image-20220418131945688

提升一个量级,但panic更慢了

闭包

image-20220418170727673

image-20220417204040147

为啥f不直接指向地址呢,还要通过一个funcval的结构体呢?

image-20220417204250074

addr1为函数A的入口地址,addr2为funcval结构体的地址,没有捕获列表的funcval在编译阶段会做出优化,就是在只读数据段分配一个共用的funcval结构体

image-20220417204233606

image-20220417204745921

除了初始化赋值外,在任何地方都没被修改,直接拷贝值到捕获列表中就行

image-20220417205254445

就是要保持捕获变量在外层函数与闭包函数中的一致性,由于捕获变量i初始化后被修改过,所以i在堆上分配,并在栈中存i的地址

Golang的sync.Map

golang sync.mutex

https://www.bilibili.com/video/BV15V411n7fM/

// 互斥锁的公平性。 // // 互斥锁有两种运行模式:正常模式和饥饿模式。 // 在正常模式下,请求锁的goroutine会按 FIFO(先入先出)的顺序排队,依次被唤醒,但被唤醒的goroutine并不能直接获取锁,而是要与新请求锁的goroutines去争夺锁的所有权。 // 但是这其实是不公平的,因为新请求锁的goroutines有一个优势:他们正在cpu上运行且数量可能比较多,所以新唤醒的goroutine在这种情况下很难获取锁。在这种情况下,如果这个goroutine获取失败,会直接插入到队列的头部。 // 如果一个等待的goroutine超过1ms时仍未获取到锁,会将这把锁转换为饥饿模式。 // // 在饥饿模式下,互斥锁的所有权会直接从解锁的goroutine转移到队首的goroutine。 // 并且新到达的goroutines不会尝试获取锁,会直接插入到队列的尾部。 // // 如果一个等待的goroutine获得了锁的所有权,并且满足以下两个条件之一: // (1) 它是队列中的最后一个goroutine // (2) 它等待的时间少于 1 毫秒(hard code在代码里) // 它会将互斥锁切回正常模式。 // // 普通模式具有更好的性能,因为即使有很多阻塞的等待锁的goroutine,一个goroutine也可以尝试请求多次锁。 // 而饥饿模式则可以避免尾部延迟这种bad case。

1
2
3
4
5
6
7
type Mutex struct {

    state int32

    sema  uint32

}
  1. state字段就是这把mutex的状态,二进制低3位对应锁的状态,将state右移3位代表waiter的数量。
  2. sema(信号量)用来唤醒goroutine。
1
2
3
4
5
6
7
8
9
// A Locker represents an object that can be locked and unlocked.

type Locker interface {

    Lock()

    Unlock()

}

lock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func (m *Mutex) Lock() {

   // fast path:这里使用了atomic的case操作,如果state的值为0(当前锁处于开启状态且无等待者),则可以直接加锁成功

   if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {

      if race.Enabled {

         race.Acquire(unsafe.Pointer(m)) // load happens-before保证

      }

      return

   }

   // Slow path:需要执行各种操作,而mutex加锁的核心也在这里

   m.lockSlow()

}

unlock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

func (m *Mutex) Unlock() {

   // 和加锁时基本同理,在无waiter且锁的模式为正常模式,会尝试直接解锁。

   new := atomic.AddInt32(&m.state, -mutexLocked)//state减去mutexLocked

   if new != 0 {

      m.unlockSlow(new)

   }

}

常见坑点

1.sync.mutex不可重入

  • 例如如果连续调用两次lock,会触发死锁,goroutine直接panic。
  • 如果A获取A+B,B获取B+A,会触发死锁

2.sync.mutex,尝试去unlock一把空闲的mutex,会导致panic。

3.sync.mutex不与goroutine绑定,可由a goroutine获取锁,b goroutine释放锁。

4.不要复制sync.Mutex,mutex做函数参数时,传参时使用指针。

gin入门

gin入门

gin官方文档