Golang 面试题 IV

坚持, 量变才会引起质变

newmake的区别?

  • new只用于分配内存,返回一个指向地址的指针。它为每个新类型分配一片内存,初始化为0且返回类型*T内存地址,它相当于&T{}
  • make只可用于slice,map,channel的初始化,返回的是引用

Go面向对象是如何实现的?

Go实现面向对象的两个关键是structinterface

  • 封装:对于同一个包,对象对包内的文件可见;对不同的包,需要将对象以大写开头才是可见的。
  • 继承: 继承是编译时特征,在struct内加入所需要继承的类即可; (组合)
  • 多态: 多态是运行时特征,Go多态通过interface来实现。类型和接口是松耦合的,某个类型的实例可以赋给它所实现的任意接口类型的变量。

Go支持多重继承,就是在类型中嵌入所有必要的父类型。

uint型变量值分别为 1,2,它们相减的结果是多少?

1
2
3
   var a uint = 1
var b uint = 2
fmt.Println(a - b)

结果会溢出,如果是32位系统,结果是2^32-1,如果是64位系统,结果2^64-1

有没有函数在main之前执行?怎么用?

init函数

1
2
3
func init() {
...
}
  • init函数非常特殊:
    • 初始化不能采用初始化表达式初始化的变量
    • 程序运行前执行注册
    • 实现sync.Once功能
    • 不能被其它函数调用
    • init函数没有入口参数和返回值
    • 每个包可以有多个init函数,每个源文件也可以有多个init函数
    • 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序
    • 不同包的init函数按照包导入的依赖关系决定执行顺序

下面这句代码是什么作用,为什么要定义一个空值?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type GobCodec struct {
conn io.ReadWriteCloser
buf *bufio.Writer
dec *gob.Decoder
enc *gob.Encoder
}

type Codec interface {
io.Closer
ReadHeader(*Header) error
ReadBody(interface{}) error
Write(*Header, interface{}) error
}

var _ Codec = (*GobCodec)(nil)

nil转换为*GobCodec类型,然后再转换为Codec接口,如果转换失败,说明*GobCodec没有实现Codec接口的所有方法。

mutex有几种模式?

mutex有两种模式:normalstarvation

正常模式

所有goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常新请求锁的goroutine更容易获取锁(持续占有cpu),被唤醒的goroutine则不容易获取到锁。公平性:否。

饥饿模式

所有尝试获取锁的goroutine进行等待排队,新请求锁的goroutine不会进行锁获取(禁用自旋),而是加入队列尾部等待获取锁。公平性:是。

go如何进行调度的。GMP中状态流转

Go里面GMP分别代表:G:goroutine,M:线程(真正在CPU上跑的),P:调度器

调度器是M和G之间桥梁。

go进行调度过程

  • 某个线程尝试创建一个新的G,那么这个G就会被安排到这个线程的G本地队列LRQ中,如果LRQ满了,就会分配到全局队列GRQ中;
  • 尝试获取当前线程的M,如果无法获取,就会从空闲的M列表中找一个,如果空闲列表也没有,那么就创建一个M,然后绑定GP运行。
  • 进入调度循环:
    • 找到一个合适的G
    • 执行G,完成以后退出

Go什么时候发生阻塞?阻塞时,调度器会怎么做。

  • 用于原子、互斥量或通道操作导致goroutine阻塞,调度器将把当前阻塞的goroutine从本地运行队列LRQ换出,并重新调度其它goroutine
  • 由于网络请求IO导致的阻塞,Go提供了网络轮询器(Netpoller)来处理,后台用epoll等技术实现IO多路复用

其他回答

  • channel阻塞: 当goroutine读写channel发生阻塞时,会调用gopark函数,该G脱离当前的M和P,调度器将新的G放入当前M。
  • 系统调用: 当某个G由于系统调用陷入内核态,该P就会脱离当前M,此时P会更新自己的状态为Psyscall,M与G相互绑定,进行系统调用。结束以后,若该P状态还是Psyscall,则直接关联该M和G,否则使用闲置的处理器处理该G。
  • 系统监控: 当某个G在P上运行的时间超过10ms时候,或者P处于Psyscall状态过长等情况就会调用retake函数,触发新的调度。
  • 主动让出: 由于是协作式调度,该G会主动让出当前的P(通过GoSched),更新状态为Grunnable,该P会调度队列中的G运行。

如果有一个G一直占用资源怎么办?

如果有个goroutine一直占用资源,那么GMP模型会从正常模式转变为饥饿模式(类似于mutex),允许其它goroutine使用work stealing抢占(禁用自旋锁)。

什么是work stealing算法?

指,一个线程如果处于空闲状态,则帮其它正在忙的线程分担压力,从全局队列取一个G任务来执行,可以极大提高执行效率。

Go竞态条件了解吗?

所谓竞态竞争,就是当两个或以上的goroutine访问相同资源时候,对资源进行读/写

比如var a int = 0,有两个协程分别对a+=1,我们发现最后a不一定为2.这就是竞态竞争

通常我们可以用go run -race xx.go来进行检测。

解决方法是,对临界区资源上锁,或者使用原子操作(atomics),原子操作的开销小于上锁。

如果若干个goroutine,有一个panic会怎么做?

有一个panic,那么剩余goroutine也会退出,程序退出。如果不想程序退出,那么必须通过调用 recover() 方法来捕获 panic 并恢复将要崩掉的程序。

defer可以捕获goroutine的子goroutine吗?

不可以
它们处于不同的调度器P中。对于子goroutine,必须通过 recover() 机制来进行恢复,然后结合日志进行打印(或者通过channel传递error),下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Ping 心跳函数
func Ping(ctx context.Context) error {
// ... code ...

go func() {
defer func() {
if r := recover(); r != nil {
log.Println(ctx, "ping panic: %v, stack: %v", r, string(debug.Stack()))
}
}()

// ... code ...
}()

// ... code ...

return nil
}

Go解析Tag是怎么实现的?

Go解析tag采用的是反射
具体来说使用reflect.ValueOf方法获取其反射值,然后获取其Type属性,之后再通过Field(i)获取第i+1个field,再.Tag获得Tag。
反射实现的原理在: src/reflect/type.go

channel死锁的场景

当一个channel中没有数据,而直接读取时,会发生死锁:

1
2
q := make(chan int,2)
<-q

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

1
2
3
4
5
6
7
q := make(chan int,2)
select{
case val:=<-q:
default:
// ...

}

说说 atomic底层怎么实现的

atomic源码位于sync\atomic。通过阅读源码可知,atomic采用CAS(CompareAndSwap)的方式实现的。
所谓CAS就是使用了CPU中的原子性操作。在操作共享变量的时候,CAS不需要对其进行加锁,而是通过类似于乐观锁的方式进行检测,总是假设被操作的值未曾改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换。本质上是不断占用CPU资源来避免加锁的开销。

go的调试/分析工具用过哪些

  • go的自带工具链相当丰富
    • go cover: 测试代码覆盖率
    • pprof: 用于性能调优,针对cpu,内存和并发;
    • godoc: 用于生成go文档
    • race:用于竞争检测

实现使用字符串函数名,调用函数。

采用反射的Call方法实现。

1
2
3
4
5
6
7
8
9
10
11
type Animal struct {
}

func (a *Animal) Eat() {
fmt.Println("Eat")
}

func main() {
a := Animal{}
reflect.ValueOf(&a).MethodByName("Eat").Call([]reflect.Value{})
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2022 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :