头图

最近有朋友私聊问我说“阳哥,最近的行情是不是变好了?”

我没有正面回复他,因为其实这个问题根本没有答案。(很抱歉我的标题骗到了你ovo)

我一直觉得讨论行情的“暖”和“寒”是没有意义的,举个很简单的例子:

当各个公司高喊“用工荒”,大量发放岗位的时候,照样有人找不到工作;当“降本增效”成为企业生存法则,各大企业都大量裁员的时候,照样有人能找到工作

说到这里大家也应该懂我的意思了吧。

最近确实经常传来喜报,这里也给大家分享一下看看:

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

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

Java零基础学员实习上岸

为了找到工作,他们都是下了很大功夫的,不能因为一句“行情变暖”就忽视他们自己的努力,所有的东西都是靠自己争取到的。

换句话讲,行情变好还是变差,和我们没有一分钱关系,只管冲就完事了。

想都是问题,做才有答案!

Go常见面试题分享

01 ❤channel 死锁的场景

  • 当一个channel中没有数据,而直接读取时,会发生死锁:
q := make(chan int,2)
<-q

解决方案是采用select语句,再default放默认处理方式:

q := make(chan int,2)
select{
   case val:=<-q:
   default:
         ...

}
  • 当channel数据满了,再尝试写数据会造成死锁:
q := make(chan int,2)
q<-1
q<-2
q<-3

解决方法,采用select

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死锁情况的汇总以及解决方案

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 - 编程猎人

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的底层实现

05 map的底层实现。

源码位于src\runtime\map.go 中。

go的map和C++map不一样,底层实现是哈希表,包括两个部分:hmapbucket

里面最重要的是buckets(桶),buckets是一个指针,最终它指向的是一个结构体:

// 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底层实现分析

06 select的实现原理?

select源码位于src\runtime\select.go,最重要的scase 数据结构为:

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)
  1. 所有case都未ready,且没有default语句
  • 3.1 将当前协程加入到所有channel的等待队列
  • 3.2 当将协程转入阻塞,等待被唤醒
  1. 唤醒后返回channel对应的case index
  • 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
  • 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)

    参考资料:Go select的使用和实现原理

07 go的interface怎么实现的?

go interface源码在runtime\iface.go中。

go的接口由两种类型实现iface和eface。iface是包含方法的接口,而eface不包含方法。

  • iface

    对应的数据结构是(位于src\runtime\runtime2.go):

type iface struct {
        tab  *itab
        data unsafe.Pointer
}

可以简单理解为,tab表示接口的具体结构类型,而data是接口的值。

  • itab
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直接表示类型,这样的话就无法使用方法。

type eface struct {
        _type *_type
        data  unsafe.Pointer
}

这里篇幅有限,深入讨论可以看:深入研究 Go interface 底层实现

08 go的reflect底层实现

go reflect源码位于src\reflect\下面,作为一个库独立存在。反射是基于接口实现的。

Go反射有三大法则:

  • 反射从接口映射到反射对象;
  • 反射从反射对象映射到接口值
  • 只有值可以修改(settable),才可以修改反射对象。

Go反射基于上述三点实现。我们先从最核心的两个源文件入手type.go和value.go.

type用于获取当前值的类型。value用于获取当前的值。

参考资料:The Laws of Reflection图解go反射实现原理

09 go GC的原理知道吗?

如果需要从源码角度解释GC,推荐阅读(非常详细,图文并茂)

欢迎关注 ❤

我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。

没准能让你能刷到自己意向公司的最新面试题呢。

感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。


王中阳讲编程
836 声望326 粉丝