据说这道Go面试题90%的人都搞错了!
【Go面试向】defer与time.sleep初探
大家好,我是阳哥,这是我们Go就业训练营小伙伴 寸铁同学 整理的一道很有意思的面试题。
知其然更要知其所以然,通过断点调试的思路带你搞清楚来龙去脉。
喜欢的小伙伴可以点点关注!
请大家看下面这段代码,看运行结果会出现什么,为什么?
问题
demo
package main
import (
"log"
"time"
)
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
看到这里,我想说同学你的思路和想法是好的,一开始我也和你一样,但是这里的答案是错的,那为什么错呢?总得有个原因吧!很明显匿名函数的时间差符合逻辑是对的,那为什么时间差与预期不符合呢?下面我来进行分析。
我的微信:wangzhongyang1993
运行结果
分析
这里的关键点在于为什么时间差
为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, 观察3
s后会光标会去到哪里?
猜想:
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 main
import (
"fmt"
"time"
)
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 main
import (
"fmt"
"time"
)
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
写一个计算函数运行时间
的程序
相信看到这里的小伙伴,已经很清楚要写什么代码了!
欢迎加我的微信:wangzhongyang1993
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
// 使用匿名函数,传递的是函数的指针
defer func() {
fmt.Println(time.Since(start))
}()
time.Sleep(3 * time.Second)
}
看到这里的小伙伴,恭喜你又掌握了一个知识点
希望大家能取得胜利,坚持就是胜利!
据说这道Go面试题90%的人都搞错了!的更多相关文章
- PriorityQueue 是线性结构吗?90%的人都搞错了!
文章首发于「陈树义」公众号及个人博客 shuyi.tech 其实这个问题的完整描述是:Java 中的 PriorityQueue 实现,其数据的逻辑结构是线性结构吗?其数据的物理结构又是什么? 估计很 ...
- Java引用类型原理深度剖析,看完文章,90%的人都收藏了
本文为synchronized系列第二篇.主要内容为分析偏向锁的实现. 偏向锁的诞生背景和基本原理在上文中已经讲过了. 本文将分为几块内容: 1.偏向锁的入口 2.偏向锁的获取流程 3.偏向锁的撤销流 ...
- 90%的人都不知道的Node.js 依赖关系管理(上)
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/nodejs-dependency-mana ...
- 90%的人都不知道的Node.js 依赖关系管理(下)
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://dzone.com/articles/node-dependency-manage ...
- 关于 junit4 90% 的人都不知道的特性,详解 junitperf 的实现原理
前言 上一节介绍了 https://github.com/houbb/junitperf 的入门使用. 这一节我们从源码的角度,剖析一下其实现方式. 性能测试该怎么做? Junit Rules jun ...
- Win7共享文件夹简单?这个共享问题可以难倒90%的人
信息化社会,没有哪个公司不用电脑办公了.一个办公室里面的同事相互之间利用系统的共享功能,共享一些文件和软件已经是司空见惯的了,这个不需要多么复杂的操作.我们使用最多的windows7操作系统就能很方便 ...
- 一道月薪3W的java面试题 (小明和小强都是张老师的学生,张老师的生日是某月某日,2人都不知道张老师的生日)
小明和小强都是张老师的学生,张老师的生日是M月N日,2人都知道张老师的生日 是下列10组中的一天,张老师把M值告诉了小明,把N值告诉了小强,张老师问他们知道他的生日是那一天吗? 3月4日 3月5日 3 ...
- 这个 Spring 循环依赖的坑,90% 以上的人都不知道
1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...
- 上传伪技术~很多人都以为判断了后缀,判断了ContentType,判断了头文件就真的安全了。是吗?
今天群里有人聊图片上传,简单说下自己的经验(大牛勿喷) 0.如果你的方法里面是有指定路径的,记得一定要过滤../,比如你把 aa文件夹设置了权限,一些类似于exe,asp,php之类的文件不能执行,那 ...
- 99%的人都理解错了HTTP中GET与POST的区别(转载
GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. 你可能自己 ...
随机推荐
- 华为云GaussDB数据库荣获国际CC EAL4+级别认证
摘要:近日,华为云GaussDB企业级分布式数据库内核正式通过了全球知名独立认证机构欧洲SGS Brightsight实验室的安全评估,获得全球权威信息技术安全性评估标准CC EAL4+级别认证. 本 ...
- 再谈P2P技术:网络拓扑结构、核心技术分析
随着P2P应用的蓬勃发展,作为P2P应用中核心问题的发现技术除了遵循技术本身的逻辑以外,也受到某些技术的发展趋势.需求趋势的深刻影响. P2P协议概述 P2P打破了传统的Client/Server ( ...
- appuploader不是开发者账号
Appuploader是一款可以帮助开发者上传iOS应用到Apple App Store的工具.很多开发者都知道,在上传应用到App Store之前,需要创建开发者账号并获得苹果官方的认证才能进行上传 ...
- 陕西旅游集团旗下景区春节期间累计接待超 200 万人次,这背后也有火山引擎 VeDI 的身影
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,并进入官方交流群 春节期间累计接待游客 200.42 万人次,同比 2022 年增长 102.47%,同比 2019 年增长 19.27%, ...
- 【第三方库】从编译到运行,轻松学会gflags库
gflags是Google开源的一个库,可以很方便地定义一些全局变量,并且可以从命令行设置他们的值,广泛应用于各个项目中以及自己平时的开发中.本期参考gflags的官方文档,简单直接介绍下怎么使用这个 ...
- 0x01 基本算法-位运算
A题:a^b https://ac.nowcoder.com/acm/contest/996/A 题目描述 求 a 的 b 次方对 p 取模的值,其中 0 <= a,b,p <= 10^9 ...
- CodeForces - 651A Joysticks ( 不难 但有坑 )
正式更换编译器为: VS Code 如何配置环境:click here 代码格式化工具:clang-format A. Joysticks 题目连接: http://www.codeforces.co ...
- 1 分钟在 Serverless 上部署现代化 Deno Web 应用
作者 | 连喆人(掌上乾坤公司) 本文选自 "Serverless 函数计算征集令" 征文 利用 Serverless 的水平扩展与按量付费优势, 结合自定义运行时, 实现 Web ...
- 10、SpringBoot-mybatis-plus-druid多源数据
系列导航 springBoot项目打jar包 1.springboot工程新建(单模块) 2.springboot创建多模块工程 3.springboot连接数据库 4.SpringBoot连接数据库 ...
- vant下拉加载更多,上拉刷新
https://www.bilibili.com/video/BV1zq4y1p7ga?p=218 List 组件通过 loading 和 finished 两个变量控制加载状态,当组件滚动到底部时, ...