Go语言面试宝典
文章平均质量分 86
白话机器学习
10年IT从业经验,人工智能高级算法工程师、CSDN博客专家、阿里云专家、《2023博客之星马龄赛道11-15年》第一名、《2023博客之星,城市赛道》长春TOP1,CSDN付费资源项目实践专家
展开
-
Go语言面试精华——main goroutine 如何创建
换句话说,每个 goroutine 都有自己的栈空间,newproc 函数会新创建一个新的 goroutine 来执行 fn 函数,在新 goroutine 上执行指令,就要用新 goroutine 的栈。我们知道,goroutine 和线程一样,都有自己的栈,不同的是 goroutine 的初始栈比较小,只有 2K,而且是可伸缩的,这也是创建 goroutine 的代价比创建线程代价小的原因。栈顶是 siz,再往上是函数的地址,再往上就是传给 hello 函数的参数,string 在这里是一个地址。原创 2023-09-12 22:42:38 · 70 阅读 · 3 评论 -
Go语言面试精华——请描述 scheduler 的初始化过程?
这一讲,我们来研究 Go sheduler 结构体,以及整个调度器的初始化过程。Go scheduler 在源码中的结构体为schedt,保存调度器的状态信息、全局的可运行 G 队列等。在程序运行过程中,schedt对象只有一份实体,它维护了调度器的所有信息。在程序初始化时,这些全局变量都会被初始化为零值:指针被初始化为 nil 指针,切片被初始化为 nil 切片,int 被初始化为 0,结构体的所有成员变量按其类型被初始化为对应的零值。原创 2023-09-12 22:40:15 · 62 阅读 · 0 评论 -
Go语言面试精华——GPM 是什么?
先看 G,取 goroutine 的首字母,主要保存 goroutine 的一些状态信息以及 CPU 的一些寄存器的值,例如 IP 寄存器,以便在轮到本 goroutine 执行时,CPU 知道要从哪一条指令处开始执行。再来看 P,取 processor 的首字母,为 M 的执行提供“上下文”,保存 M 执行 G 时的一些资源,例如本地可运行 G 队列,memeory cache 等。找不到的时候会进入非自旋状态,之后会休眠,直到有工作需要处理时,被其他工作线程唤醒,又进入自旋状态。你中有我,我中有你。原创 2023-09-12 22:32:47 · 123 阅读 · 0 评论 -
Go语言面试精华——什么是 scheduler?
Go 程序的执行由两层组成:Go Program,Runtime,即用户程序和运行时。它们之间通过函数调用来实现内存管理、channel 通信、goroutines 创建等功能。用户程序进行的系统调用都会被 Runtime 拦截,以此来帮助它进行调度以及垃圾回收相关的工作。原创 2023-09-12 22:30:25 · 89 阅读 · 0 评论 -
GO语言面试精华——关于垃圾回收的优化相关问题
Go 的 GC 被设计为成比例触发、大部分工作与赋值器并发、不分代、无内存移动且会主动向操作系统归还申请的内存。原创 2023-09-12 22:25:31 · 93 阅读 · 1 评论 -
GO语言面试精华——Go语言中GC的流程及实现细节?
当存在新的内存分配时,会暂停分配内存过快的那些 goroutine,并将其转去执行一些辅助标记(Mark Assist)的工作,从而达到放缓继续分配、辅助 GC 的标记工作的目的。除此之外,步调算法还需要考虑 CPU 利用率的问题,显然我们不应该让垃圾回收器占用过多的 CPU,即不应该让每个负责执行用户 goroutine 的线程都在执行标记过程。理想情况下,在用户代码满载的时候,GC 的 CPU 使用率不应该超过 25%,即另一个优化问题:如果设。进行控制(他们控制的是同一个变量,即堆的增长率。原创 2023-09-12 22:22:04 · 290 阅读 · 0 评论 -
GO语言面试精华——谈谈你对垃圾回收机制的认识?
要讲清楚写屏障,就需要理解三色标记清除算法中的强弱不变性以及赋值器的颜色,理解他们需要一定的抽象思维。不应出现对象的丢失,也不应错误的回收还不需要回收的对象。条件 1: 赋值器修改对象图,导致某一黑色对象引用白色对象;条件 2: 从灰色对象出发,到达白色对象的、未经访问过的路径被赋值器破坏。如果条件 1 被避免,则所有白色对象均被灰色对象引用,没有白色对象会被遗漏;原创 2023-09-12 22:19:46 · 114 阅读 · 0 评论 -
GO语言面试精华——如何实现两种 get 操作?
这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。如果通过其他 hack 的方式,例如 unsafe.Pointer 等获取到了 key 或 value 的地址,也不能长期持有,因为一旦发生扩容,key 和 value 的位置就会改变,之前保存的地址也就失效了。原创 2023-09-11 09:44:33 · 81 阅读 · 4 评论 -
GO语言面试精华——map 的赋值过程、删除过程是怎样的?
map 在扩容后,会发生 key 的搬迁,原来落在同一个 bucket 中的 key,搬迁后,有些 key 就要远走高飞了(bucket 序号加上了 2^B)。这三者实际上都是关联的,在 tophash 数组中的索引位置决定了 key 在整个 bucket 中的位置(共 8 个 key),而 value 的位置需要“跨过” 8 个 key 的长度。上面说的操作是在函数靠前的位置进行的,只有进行完了这个搬迁操作后,我们才能放心地在新 bucket 里定位 key 要安置的地址,再进行之后的操作。原创 2023-09-11 09:25:41 · 67 阅读 · 0 评论 -
GO语言面试精华——float 类型可以作为 map 的 key 吗?
接着,我们查询了几个 key,发现 NAN 不存在,2.400000000001 也不存在,而 2.4000000000000000000000001 却存在。关于当 key 是引用类型时,判断两个 key 是否相等,需要 hash 后的值相等并且 key 的字面量相等。例子中定义了一个 key 类型是 float 型的 map,并向其中插入了 4 个 key:1.4, 2.4, NAN,NAN。最后说结论:float 型可以作为 key,但是由于精度的问题,会导致一些诡异的问题,慎用之。原创 2023-09-11 09:20:32 · 108 阅读 · 1 评论 -
GO语言面试精华——map 的扩容过程是怎样的?
这样的循环在 map 的源码里到处都是,要理解透了。对于条件 2 的解决方案,曹大的博客里还提出了一个极端的情况:如果插入 map 的 key 哈希都一样,就会落到同一个 bucket 里,超过 8 个就会产生 overflow bucket,结果也会造成 overflow bucket 数过多。对于条件 2,从老的 buckets 搬迁到新的 buckets,由于 bucktes 数量不变,因此可以按序号来搬,比如原来在 0 号 bucktes,到新的地方后,仍然放在 0 号 buckets。原创 2023-09-11 09:19:24 · 119 阅读 · 0 评论 -
GO语言面试精华——map 的遍历过程是怎样的?
0 号 bucket 对应老的 0 号 bucket,经检查,老 0 号 bucket 并未搬迁,因此对新 0 号 bucket 的遍历就改为遍历老 0 号 bucket。这种的,处理方式类似。继续遍历新 2 号 bucket,它来自老 0 号 bucket,因此需要在老 0 号 bucket 中那些会裂变到新 2 号 bucket 中的 key,也就是。和之前的流程一样,继续遍历新 1 号 bucket,发现老 1 号 bucket 已经搬迁,只用遍历新 1 号 bucket 中现有的元素就可以了。原创 2023-09-11 09:12:51 · 92 阅读 · 0 评论 -
Go语言面试精华——接口转换的原理是什么?
当判定一种类型是否满足某个接口时,Go 使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。这里我们来探索将一个接口转换给另外一个接口背后的原理,当然,能转换的原因必然是类型兼容。的情况下,需要新生成一个,并且写入哈希表,因此需要加锁。并且也没有找到时,第二次查找时,会被挂住,之后,就会查到第一个协程写入哈希表的。,Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为。更一般的,当把实体类型赋值给接口的时候,会调用。原创 2023-09-10 21:14:02 · 38 阅读 · 1 评论 -
GO语言面试精华——类型转换和断言的区别?
我们知道,Go 语言中不允许隐式类型转换,也就是说两边,不允许出现类型不相同的变量。类型转换类型断言本质都是把一个类型转换成另外一个类型。不同之处在于,类型断言是对接口变量进行的操作。原创 2023-09-10 21:13:10 · 49 阅读 · 0 评论 -
GO语言面试精华——接口的构造过程是怎样的?
下面两行是链接指令,简单说就是将所有源文件综合起来,给每个符号赋予一个全局的位置值。这里的意思也比较明确:前8个字节最终存储的是。我们从第 10 行开始看,如果不理解前面几行汇编代码的话,可以回去看看公众号前面两篇文章,这里我就省略了。序列化后的内容,注意到大部分数字是 0,从 24 字节开始的 4 个字节。为了研究清楚接口是如何构造的,接下来我会拿起汇编的武器,还原背后的真相。函数及之前的参数准备工作了,不再赘述。,对比一下正宗的定义就可以发现,但是。值依然不变的,这应该是可以预料的,原创 2023-09-10 21:12:13 · 36 阅读 · 0 评论 -
GO语言面试精华——接口的动态类型和动态值?
函数中,直接调用接口函数,实际执行的时候是看最终传入的实体类型是什么,调用的是实体类型实现的函数。C++ 定义接口的方式称为“侵入式”,而 Go 采用的是 “非侵入式”,不需要显式声明,只需要实现接口定义的函数,编译器自动会识别。实际上,上述赋值语句会发生隐式地类型转换,在转换的过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数。数组里保存的是实体类型实现的函数,所以当函数传入不同的实体类型时,调用的实际上是不同的函数实现,从而实现多态。b 的动态类型和 c 的动态类型一致,都是。原创 2023-09-10 21:10:57 · 62 阅读 · 0 评论 -
Go语言面试精华——iface 和 eface 的区别是什么?Go 语言与鸭子类型的关系?
实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。动态语言则没有这些要求,可以让人更专注在业务上,代码也更短,写起来更快,这一点,写 python 的同学比较清楚。总结一下,鸭子类型是一种动态语言的风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由它"当前方法和属性的集合"决定。字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。原创 2023-09-10 21:06:52 · 30 阅读 · 0 评论 -
GO语言面试精华——值接收者和指针接收者的区别?
方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。调用了growUp函数后,不管调用者是值类型还是指针类型,它的Age值都改变了。原创 2023-09-10 21:02:28 · 123 阅读 · 1 评论 -
GO语言面试精华——Go 程序启动过程是怎样的?
这里 LEAQ 是计算内存地址,然后把内存地址本身放进寄存器里,也就是把 argv 的地址放到了 SI 寄存器中。main 函数里执行的一些重要的操作包括:新建一个线程执行 sysmon 函数,定期垃圾回收和调度抢占;是为了关闭编译器优化和函数内联,防止后面在设置断点的时候找不到相对应的代码位置。正常情况下,一旦出现非法地址访问,系统会把进程杀死,用这样的方法确保进程退出。这就是 Go 程序的入口地址,我是在 linux 上运行的,所以入口文件为。同时,我们也得到了入口地址:0x450e20。原创 2023-09-10 21:00:33 · 50 阅读 · 1 评论 -
GO语言面试精华——Go 编译相关的命令详解
直接在终端执行:就能得到和 go 相关的命令简介:和编译相关的命令主要是:go build 用来编译指定 packages 里的源码文件以及它们的依赖包,编译的时候会到 路径下寻找源码文件。 还可以直接编译指定的源码文件,并且可以同时指定多个。通过执行 命令得到 的使用方法: 只能在编译单个包的时候出现,它指定输出的可执行文件的名字。 会安装编译目标所依赖的包,安装是指生成与代码包相对应的 文件,即静态库文件(后面要参与链接),并且放置到当前工作区的 pkg 目录下,且库文件的目录层级和源码原创 2023-09-10 20:52:34 · 70 阅读 · 1 评论 -
Go语言面试精华——Go 编译链接过程概述
我们从一个当我们用键盘敲完上面的 hello world 代码时,保存在硬盘上的hello.go文件就是一个字节序列了,每个字节代表一个字符。:%!xxd就能在 vim 里以十六进制查看文件内容:最左边的一列代表地址值,中间一列代表文本对应的 ASCII 字符,最右边的列就是我们的代码。再在终端里执行man ascii和 ASCII 字符表一对比,就能发现,中间的列和最右边的列是一一对应的。也就是说,刚刚写完的 hello.go 文件都是由 ASCII 字符表示的,它被称为文本文件,其他文件被称为。原创 2023-09-10 20:48:55 · 70 阅读 · 0 评论 -
GO语言面试精华——逃逸分析是怎么进行的?
和上一个例子相比,有一点小差别,但是导致的程序效果是不同的:例子4中,i先在main的栈帧中分配,之后又在refStruct栈帧中分配,然后又逃逸到堆上,到堆上分配了一次,共3次分配。这些局部变量是在栈上分配的(静态内存分配),一旦函数执行完毕,变量占据的内存会被销毁,任何对这个返回值作的动作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提高程序的运行速度。原创 2023-09-10 20:45:06 · 27 阅读 · 0 评论 -
GO语言面试精华——关于 channel 的 happened-before 有哪些?channel有哪些应用?
维基百科上给的定义:简单来说就是如果事件 a 和事件 b 存在 happened-before 关系,即 a -> b,那么 a,b 完成后的结果一定要体现这种关系。由于现代编译器、CPU 会做各种优化,包括编译器重排、内存重排等等,在并发代码里,happened-before 限制就非常重要了。sendreceivereceive我们来逐条解释一下。第一条,我们从源码的角度看也是对的,send 不一定是。原创 2023-09-10 20:42:47 · 49 阅读 · 0 评论 -
GO语言面试精华——如何优雅地关闭 channel?
因此需要增加一个中间人,M 个 receiver 都向它发送关闭 dataCh 的“请求”,中间人收到第一个请求后,就会直接下达关闭 dataCh 的指令(通过关闭 stopCh,这时就不会发生重复关闭的情况,因为 stopCh 的发送方只有中间人一个)。例如,IsClosed 函数返回 true,但这时有另一个 goroutine 关闭了 channel,而你还拿着这个过时的 “channel 未关闭”的信息,向其发送数据,就会导致 panic 的发生。这不是一个好的函数,干活就干活,还顺手牵羊!原创 2023-09-10 20:40:13 · 49 阅读 · 0 评论 -
Go语言面试精华——关闭一个 channel 的过程是怎样的?
就是说 channel 的发送和接收操作本质上都是 “值的拷贝”,无论是从 sender goroutine 的栈到 chan buf,还是从 chan buf 到 receiver goroutine,或者是直接从 sender goroutine 到 receiver goroutine。先创建了一个有缓冲的 channel,向其发送一个元素,然后关闭此 channel。,它是指针 g 的值(不是它指向的内容),所以打印从 channel 接收到的元素时,它就是。关闭某个 channel,会执行函数。原创 2023-09-10 20:39:00 · 42 阅读 · 0 评论 -
GO语言面试精华——从 channel 接收数据的过程是怎样的?
我们先来看一下接收相关的源码。在清楚了接收的具体过程之后,再根据一个实际的例子来具体研究。接收操作有两种写法,一种带 “ok”,反应 channel 是否关闭;一种不带 “ok”,这种写法,当接收到相应类型的零值时无法知道是真实的发送者发送过来的值,还是 channel 被关闭后,返回给接收者的默认类型的零值。两种写法,都有各自的应用场景。函数处理不带 “ok” 的情形,chanrecv2则通过返回 “received” 这个字段来反应 channel 是否被关闭。接收值则比较特殊,会“放到”参数。原创 2023-09-10 20:35:06 · 86 阅读 · 0 评论 -
Go语言面试精华——向channel 发送数据的过程是怎样的?
发送操作最终转化为chansend上面的代码注释地比较详细了,我们来详细看看。如果检测到 channel 是空的,当前 goroutine 会被挂起。对于不阻塞的发送操作,如果 channel 未关闭并且没有多余的缓冲空间(说明:a. channel 是非缓冲型的,且等待接收队列里没有 goroutine;b. channel 是缓冲型的,但循环数组已经装满了元素)对于这一点,runtime 源码里注释了很多。这一条判断语句是为了在不阻塞发送的场景下快速检测到发送失败,好快速返回。原创 2023-09-10 20:31:44 · 85 阅读 · 0 评论 -
Go语言面试精华——请详述channel底层的数据结构是什么?
buf指向底层循环数组,只有缓冲型的 channel 才有。sendxrecvx均指向底层循环数组,表示当前可以发送和接收的元素位置索引值(相对于底层数组)。sendqrecvq分别表示被阻塞的 goroutine,这些 goroutine 由于尝试读取 channel 或向 channel 发送数据而被阻塞。waitq是sudog的一个双向链表,而sudog用来保证每个读 channel 或写 channel 的操作都是原子的。原创 2023-09-10 20:29:24 · 48 阅读 · 0 评论