这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
一、Defer问题
defer数据结构:
type _defer struct {
sp uintptr //函数栈指针
pc uintptr //程序计数器
fn *funcval //函数地址
link *_defer //指向自身结构的指针,用于链接多个defer
}
defer后面一定要接一个函数,所以defer的数据结构更一般函数类似,也有栈指针、程序计数器、函数地址等等。与函数不同的是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer就从单链表表头取出一个defer执行。
如图所示,新声明的defer(B())总是添加到链表头部,函数返回前执行defer则是从链表首部依次取出执行,形成一个栈结构。
defer的功能:
defer用来声明一个延迟函数,可以定义多个延时函数,这些函数会放入到一个栈中,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
通常用defer来做一些资源的释放,比如关闭io操作。
defer使用有几个需注意的点:
详情可查阅本人博客:defer使用细节
二、WaitGroup问题
1、waitGroup对象做值传递
如:
func main(){
var swg sync.WaitGroup
for i:=0;i<3;i++{
swg.Add(1)
go func(wg sync.WaitGroup,mark int){
defer wg.Done()
fmt.Printf("%d goroutine finish \n",mark)
}(swg,i)
}
swg.Wait()
}
//运行结果:
// 0 goroutine finish
// 1 goroutine finish
// 2 goroutine finish
// fatal error: all goroutines are asleep - deadlock!
子协程中传入的waitGroup对象的一份新值拷贝,在main主协程的waitGroup对象并没有被调用Done()方法,导致标志位无法被释放,最后发生死锁。
解决方法:将waitGroup对象做参数传递时,使用其引用拷贝传入。
2、仅在子协程goroutine中进行add操作
如:
func main() {
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
go func(wg *sync.WaitGroup, i int) {
wg.Add(1)
log.Printf("i:%d", i)
wg.Done()
}(&wg, i)
}
wg.Wait()
log.Println("exit")
}
//运行结果(可能出现):
// 2022/05/09 09:38:36 exit
因为子协程跟main主协程同步进行,可能子协程中还没来得及add(1),mian主线程就已经执行结束了。
解决方法:尽量不要仅在子协程中进行add操作。