分享
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
最近有朋友私聊问我说"阳哥,最近的行情是不是变好了?"

我没有正面回复他,因为其实这个问题**根本没有答案**。(很抱歉我的标题骗到了你ovo)
我一直觉得讨论行情的"暖"和"寒"是没有意义的,举个很简单的例子:
当各个公司高喊"用工荒",大量发放岗位的时候,**照样有人找不到工作**;当"降本增效"成为企业生存法则,各大企业都大量裁员的时候,**照样有人能找到工作**。
说到这里大家也应该懂我的意思了吧。
最近确实**经常传来喜报**,这里也给大家分享一下看看:

> 从简历已读不回,到加入组织后一个月内到手offer

> 35岁+程序员,一周到手俩offer

> Java零基础学员实习上岸
为了找到工作,他们都是下了很大功夫的,不能因为一句"行情变暖"就忽视他们自己的努力,所有的东西都是靠自己争取到的。
换句话讲,行情变好还是变差,和我们没有一分钱关系,只管冲就完事了。
**想都是问题,做才有答案!**
### Go常见面试题分享
### 01 ❤channel 死锁的场景
- 当一个channel中没有数据,而直接读取时,会发生死锁:
```Go
q := make(chan int,2)
<-q
```
解决方案是采用select语句,再default放默认处理方式:
```Go
q := make(chan int,2)
select{
case val:=<-q:
default:
...
}
```
- 当channel数据满了,再尝试写数据会造成死锁:
```Go
q := make(chan int,2)
q<-1
q<-2
q<-3
```
解决方法,采用select
```Go
func main() {
q := make(chan int, 2)
q <- 1
q <- 2
select {
case q <- 3:
fmt.Println("ok")
default:
fmt.Println("wrong")
}
}
```
- 向一个关闭的channel写数据。
注意:一个已经关闭的channel,只能读数据,不能写数据。
参考资料:[Golang关于channel死锁情况的汇总以及解决方案](https://blog.csdn.net/qq_35976351/article/details/81984117)
### 02 ❤对已经关闭的chan进行读写会怎么样?
- 读已经关闭的chan能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
- 如果chan关闭前,**buffer内有元素还未读**,会正确读到chan内的值,且返回的第二个bool值(是否读成功)为true。
- 如果chan关闭前,**buffer内有元素已经被读完**,chan内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个bool值一直为false。
写已经关闭的chan会panic。
### 03 说说atomic底层怎么实现的.
atomic源码位于`sync\atomic`。通过阅读源码可知,atomic采用**CAS**(CompareAndSwap)的方式实现的。所谓CAS就是使用了CPU中的原子性操作。在操作共享变量的时候,CAS不需要对其进行加锁,而是通过类似于乐观锁的方式进行检测,总是假设被操作的值未曾改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换。本质上是**不断占用CPU资源来避免加锁的开销**。
参考资料:[Go语言的原子操作atomic - 编程猎人](https://www.programminghunter.com/article/37392193442/)
### 04 channel底层实现?是否线程安全。
channel底层实现在src/runtime/chan.go中
channel内部是一个循环链表。内部包含buf, sendx, recvx, lock ,recvq, sendq几个部分;
buf是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表;
- sendx和recvx用于记录buf这个循环链表中的发送或者接收的index;
- lock是个互斥锁;
- recvq和sendq分别是接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表。
channel是**线程安全**的。
参考资料:[Kitou:Golang 深度剖析 -- channel的底层实现](https://zhuanlan.zhihu.com/p/264305133)
### 05 map的底层实现。
源码位于src\runtime\map.go 中。
go的map和C++map不一样,底层实现是哈希表,包括两个部分:**hmap**和**bucket**。
里面最重要的是buckets(桶),buckets是一个指针,最终它指向的是一个结构体:
```Go
// A bucket for a Go map.
type bmap struct {
tophash [bucketCnt]uint8
}
```
每个bucket固定包含8个key和value(可以查看源码bucketCnt=8).实现上面是一个固定的大小连续内存块,分成四部分:每个条目的状态,8个key值,8个value值,指向下个bucket的指针。
创建哈希表使用的是makemap函数.map 的一个关键点在于,**哈希函数**的选择。在程序启动时,会检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash。这是在函数 alginit() 中完成,位于路径:src/runtime/alg.go 下。
map查找就是将key哈希后得到64位(64位机)用最后B个比特位计算在哪个桶。在 bucket 中,从前往后找到第一个空位。这样,在查找某个 key 时,先找到对应的桶,再去遍历 bucket 中的 key。
关于map的查找和扩容可以参考[map的用法到map底层实现分析](https://blog.csdn.net/chenxun_2010/article/details/103768011?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0.pc_relevant_aa&spm=1001.2101.3001.4242.1&utm_relevant_index=3)。
### 06 select的实现原理?
select源码位于src\runtime\select.go,最重要的scase 数据结构为:
```Go
type scase struct {
c *hchan // chan
elem unsafe.Pointer // data element
}
```
scase.c为当前case语句所操作的channel指针,这也说明了一个case语句只能操作一个channel。
scase.elem表示缓冲区地址:
- caseRecv : scase.elem表示读出channel的数据存放地址;
- caseSend : scase.elem表示将要写入channel的数据存放地址;
select的主要实现位于:select.go函数:其主要功能如下:
1. 锁定scase语句中所有的channel
2. 按照随机顺序检测scase中的channel是否ready
- 2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
- 2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
- 2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
3. 所有case都未ready,且没有default语句
- 3.1 将当前协程加入到所有channel的等待队列
- 3.2 当将协程转入阻塞,等待被唤醒
4. 唤醒后返回channel对应的case index
- 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
- 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)
参考资料:[Go select的使用和实现原理](https://www.cnblogs.com/wuyepeng/p/13910678.html)
### 07 go的interface怎么实现的?
go interface源码在`runtime\iface.go`中。
go的接口由两种类型实现iface和eface。iface是包含方法的接口,而eface不包含方法。
- iface
对应的数据结构是(位于`src\runtime\runtime2.go`):
```Go
type iface struct {
tab *itab
data unsafe.Pointer
}
```
可以简单理解为,tab表示接口的具体结构类型,而data是接口的值。
- `itab`:
```Go
type itab struct {
inter *interfacetype //此属性用于定位到具体interface
_type *_type //此属性用于定位到具体interface
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
```
属性interfacetype类似于_type,其作用就是interface的公共描述,类似的还有maptype、arraytype、chantype...其都是各个结构的公共描述,可以理解为一种外在的表现信息。interfaetype和type唯一确定了接口类型,而hash用于查询和类型判断。fun表示方法集。
- eface
与iface基本一致,但是用_type直接表示类型,这样的话就无法使用方法。
```Go
type eface struct {
_type *_type
data unsafe.Pointer
}
```
这里篇幅有限,深入讨论可以看:[深入研究 Go interface 底层实现](https://halfrost.com/go_interface/#toc-1)
### 08 go的reflect底层实现
go reflect源码位于src\reflect\下面,作为一个库独立存在。反射是基于**接口**实现的。
Go反射有三大法则:
- 反射从**接口**映射到**反射对象;**
- 反射从**反射对象**映射到**接口值**;
- 只有**值可以修改**(settable),才可以**修改**反射对象。
Go反射基于上述三点实现。我们先从最核心的两个源文件入手type.go和value.go.
type用于获取当前值的类型。value用于获取当前的值。
参考资料:[The Laws of Reflection](https://go.dev/blog/laws-of-reflection), [图解go反射实现原理](https://i6448038.github.io/2020/02/15/golang-reflection/)
### 09 go GC的原理知道吗?
如果需要从源码角度解释GC,[推荐阅读](https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/)(非常详细,图文并茂)
## 欢迎关注 ❤
我们搞了一个**免费的面试真题共享群**,互通有无,一起刷题进步。
**没准能让你能刷到自己意向公司的最新面试题呢。**
感兴趣的朋友们可以加我微信:**wangzhongyang1993**,备注:面试群。
有疑问加站长微信联系(非本文作者))
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
关注微信505 次点击
上一篇:完结10章WeNet语音识别实战
下一篇:最美临时邮箱
添加一条新回复
(您需要 后才能回复 没有账号 ?)
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码` - 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传