golang垃圾回收
1:存在STW的三色标记法
程序起初创建的对象都设为白色
GC开始时从根结点开始遍历所有对象,把遍历(非递归遍历)到的对象从白色标记为灰色
遍历灰色对象,将灰色对象引用的对象标记为灰色,同时将该灰色对象标记为黑色
重复上一步骤,直到没有灰色标记的对象
将剩余所有白色结点进行删除回收
2:强-弱三色不变式
2.1强三色不变式
强制不允许黑色对象引用白色对象
2.2弱三色不变式
所有被黑色对象引用的白色对象都被灰色对象保护着
变量内存分配逃逸分析
Go 中变量分配在堆还是栈上是由编译器决定的,这种由编译器决定内存分配位置的方式称之为逃逸分析(escape analysis)。Go 中声明一个函数内局部变量时,当编译器发现变量的作用域没有逃出函数范围时,就会在栈上分配内存,反之则分配在堆上,逃逸分析由编译器完成,作用于编译阶段。
函数使用值与指针返回时性能的差异
在函数中定义变量并使用值返回时,该变量会在栈上分配内存,函数返回时会拷贝整个对象,使用指针返回时变量在分配内存时会逃逸到堆中,返回时只会拷贝指针地址,最终变量会通过 Go 的垃圾回收机制回收掉。
那在函数中返回时是使用值还是指针,哪种效率更高呢,虽然值有拷贝操作,但是返回指针会将变量分配在堆上,堆上变量的分配以及回收也会有较大的开销。对于该问题,跟返回的对象和平台也有一定的关系,不同的平台需要通过基准测试才能得到一个比较准确的结果。
缓冲通道
1、无缓冲:
要求发送方和接收方的goroutine同时准备好,才能完成发送和接收操作。
如果两个goroutine都没有准备好,通道会导致先执行发送或者接收的goroutine阻塞等待,这种对通道进行发送跟接收的交互行为本身就是同步的。
2:有缓冲的通道:make 函数创建缓冲信道指定容量
缓冲信道的容量:
是指信道可以存储的值的数量。我们在使用 make 函数创建缓冲信道的时候会指定容量大小。
读已经关闭的channel无影响。
如果在关闭前,通道内部有元素,会正确读到元素的值;如果关闭前通道无元素,则会读取到通道内元素类型对应的零值。
若遍历通道,如果通道未关闭,读完元素后,会报死锁的错误。
fatal error: all goroutines are asleep - deadlock!
写已关闭的通道
会引发panic: send on closed channel
从已关闭的 channel 接收数据,有缓冲通道返回已缓冲数据 无缓冲通道返回零值。
GMP
Go使用的是GMP模型,由GM模型演变而来
- G:Goroutine
- M:Machine,操作系统的执行线程
- P:调度器,处理M与G的关系
G
- 类似操作系统中的线程
- 提供于用户态,粒度更小,切换代价更小
- 占用空间更小,切换代价更小
M
- P最多可以创建10000个线程
- 最多只有GOMAXPROCS个活跃线程(与核数一致),这样不会频繁地切换线程上下文
P
- 调度线程上执行的G,可以让出那些等待资源(如网络、IO)的G,提高运行效率
- 同时提供M执行所需要的上下文环境以及资源
Golang锁
golang中的锁分为互斥锁、读写锁、原子锁即原子操作。在 Golang 里有专门的方法来实现锁,就是 sync 包,这个包有两个很重要的锁类型。一个叫 Mutex, 利用它可以实现互斥锁。一个叫 RWMutex,利用它可以实现读写锁。
全局锁 sync.Mutex,是同一时刻某一资源只能上一个锁,此锁具有排他性,上锁后只能被此线程使用,直至解锁。加锁后即不能读也不能写。全局锁是互斥锁,即 sync.Mutex 是个互斥锁。
读写锁 sync.RWMutex ,将使用者分为读者和写者两个概念,支持同时多个读者一起读共享资源,但写时只能有一个,并且在写时不可以读。理论上来说,sync.RWMutex 的 Lock() 也是个互斥锁。
踩坑点
将上面的结论展开一下,更清晰得说(为避免理解偏差宁可唠叨一些):
sync.Mutex 的锁是不可以嵌套使用的。
sync.RWMutex 的 mu.Lock() 是不可以嵌套的。
sync.RWMutex 的 mu.Lock() 中不可以嵌套 mu.RLock()。(这是个注意的地方)
进程,线程,协程
进程是操作系统资源分配的基本单位
线程是操作系统调度到CPU中执行的基本单位
线程是根据CPU时间片进行抢占式调度的 操作系统用户态与内核态的切换、默认的栈大小一般为8MB
协程存在于用户态,由go语言运行时调度器进行调度 不需要经过用户态与内核态的切换、go协程栈大小默认为2KB
安全类型
slice和map不是协程安全的需要加锁mutex,channel、指针、函数是协程安全的。
context
context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等
WithValue 创建 context 节点的过程实际上就是创建链表节点的过程。两个节点的 key 值是可以相等的,但它们是两个不同的 context 节点。查找的时候,会向上查找到最后一个挂载的 context 节点,也就是离得比较近的一个父节点 context。所以,整体上而言,用 WithValue 构造的其实是一个低效率的链表。
并发执行子任务且限制并发数
/*有编号为1-100 的共100个任务,每个任务打印一次 自己的编号即可,请实现如何让100个任务并发执行,但是同一时刻最多运行10个任务*/ func main () { var wg sync.WaitGroup//保证当前进程同步进行 sem := make(chan struct{},10)//保证10个一组并发执行,但是不会超过该缓冲区 for i:=1 ;i <=100 ; i++ { wg.Add(1) go func(i int) { defer wg.Done() sem <- struct{}{}//写入缓冲区 fmt.Println(i) defer func() {<-sem}()//取出缓冲区 }(i) } wg.Wait()//知道group计数器从100减到0 done就是add(-1) close(sem) }
defer 面试题
defer是延迟函数,执行动作在return之后,defer相当于是将执行动作压入栈中,越是后面的defer越是先执行,执行顺序是LIFO(后进先出) ,defer可以修改函数最终返回值。 作者:亦一银河 https://www.bilibili.com/read/cv12597493 出处:bilibili
func deferFunc1(i int) (t int) {
t = i
defer func() {
t += 1
}()
return t
}
func deferFunc2(i int) int {
t := i
defer func() {
t += 1
}()
return t
}
func deferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 1
}
func deferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 0
return 1
}
func ExecDeferFunc() {
// 猜猜下面输出的内容和顺序
fmt.Println(deferFunc1(1))
fmt.Println(deferFunc2(1))
fmt.Println(deferFunc3(1))
deferFunc4()
}
『实际结果是:2,1,2,0,1
deferFunc1 由于t作用于在函数中且作为返回值,所以,先return修改函数返回值t为1 然后defer 再接收到t并+1返回2
deferFunc2 由于t作用于在函数中但是不作为返回值,所以,defer 接收到t为0+1返回1
deferFunc3 由于t作用于在函数中且作为返回值,所以,先return修改函数返回值t为1 然后defer 再接收到t并+1返回2
deferFunc4 由于i没有定义defer打印为默认值0 而t在函数返回值被赋值为1 然后传入defer中打印为1
Gin洋葱模型
make和new的区别
make和new都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。make返回的是引用类型本身;而new返回的是指向类型的指针
range中文字符串
golang中序遍历