德宏网站制作,漯河市郾城区网站建设,办公室装修合同范本,找人做网站 网站定制开发【Go面试向】defer与time.sleep初探 大家好 我是寸铁#x1f44a; 总结了一篇defer传参与time.sleep初探的文章✨ 喜欢的小伙伴可以点点关注 #x1f49d; 请大家看下面这段代码#xff0c;看运行结果会出现什么#xff0c;为什么#xff1f;
问题
demo
package mainim…【Go面试向】defer与time.sleep初探 大家好 我是寸铁 总结了一篇defer传参与time.sleep初探的文章✨ 喜欢的小伙伴可以点点关注 请大家看下面这段代码看运行结果会出现什么为什么
问题
demo
package mainimport (logtime
)func main() {start : time.Now()defer func() {log.Printf(匿名函数时间差: %v, time.Since(start))}()defer log.Printf(时间差: %v, time.Since(start))time.Sleep(3 * time.Second)log.Printf(函数结束)
}这里不少同学会认为这题我会先输出函数结束待整个函数结束后根据defer后进先出的顺序依次打印计算的时间差由于这里睡眠了3秒两个时间差应该都是3秒左右。 所以答案为
函数结束
时间差3s
匿名函数时间差3s看到这里我想说同学你的思路和想法是好的一开始我也和你一样但是这里的答案是错的那为什么错呢总得有个原因吧很明显匿名函数的时间差符合逻辑是对的那为什么时间差与预期不符合呢下面我来进行分析。 运行结果 分析
这里的关键点在于为什么时间差为0也就是说为什么时间差这个defer语句没有被time.sleep 所影响进一步分析就是查看start的赋值时机在哪是在一开始调用defer就赋值,还是说在函数结束后给defer中的start赋值,从而造成结果的不同。 带着这个问题不妨来debug一下看一下函数语句的执行顺序。
设置断点
要想看一下start的赋值时机设置断点在出现start变量前面即可。 设置好断点后开始进行debug debug查看
step1
一开始先初始化start的值
step2
接着来到第一个defer 匿名函数看它有没有进去里面的printf语句给start变量进行赋值step over 直接跳过了匿名函数的defer语句也就是说明并没有给匿名函数中的defer语句中的start赋值 step3
之后进入defer语句中给time.Since中的start进行赋值。 这里会发现问题的关键所在对比defer 匿名函数 一开始调用(非执行)时不会对里面的变量(参数)start进行赋值。 然而普通的defer printf则在一开始时就会对里面的变量start赋值赋值后不会先把time.since的结果计算出来会在defer调用后也就是光标移动到下一条语句时调用time.sleep计算时间差,其结果就是0s 左右此时不会在调用时输出待整个函数执行完毕退出后依次按照defer 顺序输出。 step4
进一步来到了time.sleep ,这也说明step3 确实给defer 语句赋值了并没有跳过现在开始休眠3 s 观察3s后会光标会去到哪里 猜想3s 后会先输出函数结束 之后函数退出开始执行defer 语句. 结合匿名函数的时间差为3s左右,又因为defer 匿名函数中的start 还未赋值会回到开头的defer 匿名函数进行赋值。 等待3s钟 step5
3s后来到了输出函数结束的语句。 进一步函数退出,也就是整个函数执行完毕 step6
又回到了刚才的defer 匿名函数 果然与step4的猜想一致 关键点来了 如下图这里会进入defer 匿名函数 并给里面的start变量赋值。 注意这里传参start 还是一开始的start 只不过time.since(start) 的time 增加(休眠)了3s 这也很好的解释了为什么defer 匿名函数 输出的是3s 左右,而defer printf 语句却是输出0s 左右。 之后匿名函数结束 退出当前的整个函数 最后整个函数结束,输出debug 的结果 这里加了一些debug 调试的时间以运行结果为准见下。 运行结果如下 探讨
在debug 后我们来探讨一下为什么会出现这样的情况?为什么结果会有所不同里面的机制是什么 defer 输出语句
传值时机一开始调用defer 时传入
Go 语言中所有的函数调用都是传值的
虽然 defer 是关键字但是也继承了这个特性。假设我们想要计算 main 函数运行的时间可能会写出以下的代码
package mainimport (fmttime
)func main() {start : time.Now()// 这里误以为startedAt是在time.Sleep之后才会将参数传递给defer所在语句的函数中defer fmt.Println(time.Since(start))time.Sleep(3 * time.Second)
}关键点调用defer关键字会立刻拷贝函数中引用的外部参数
所以 time.Since(start) 的结果不是在 main 函数退出之前计算的而是在 defer 关键字调用时赋值计算的最终导致上述代码输出 0s。 defer 匿名函数
传值时机main函数结束后执行defer函数 时传入传入函数指针
package mainimport (fmttime
)func main() {start : time.Now()// 使用匿名函数传递的是函数的指针defer func() {fmt.Println(time.Since(start))}()time.Sleep(3 * time.Second)
}那为什么使用匿名函数就可以输出3s 呢 关键点defer 使用匿名函数 , 传递的是函数的指针(函数是一种指针类型),结合刚才的断点分析不会在一开始调用defer 匿名函数的时候就直接赋值而是在后面main函数退出时再执行defer 匿名函数 时再传入函数的指针并给start变量赋值。
总结
总而言之一开始调用defer输出语句会进行传值会在调用defer 的时候就直接传递值的拷贝(如刚才的defer输出语句)从而计算时间差。 调用defer 匿名函数 传递的是指针类型(如函数、匿名函数) 会在main函数退出时在执行defer 匿名函数 时再传入函数的指针并给里面的变量赋值。
归根结底是传入的类型不同继而导致传入变量的时机不同造成输出的结果不同
扩展
面试官请你用defer与time.sleep写一个计算函数运行时间的程序 相信看到这里的小伙伴已经很清楚要写什么代码了
package mainimport (fmttime
)func main() {start : time.Now()// 使用匿名函数传递的是函数的指针defer func() {fmt.Println(time.Since(start))}()time.Sleep(3 * time.Second)
}看到这里的小伙伴恭喜你又掌握了一个知识点 希望大家能取得胜利坚持就是胜利 我是寸铁我们下期再见