2.Golang面试题—面向对象、语言类库、并发编程、IO、通信/网络、内存分配/垃圾回收

本文目录如下:

    • 四、面向对象
      • 如何理解面向对象编程?
      • Go语言里如何实现面向对象 (实现三大特性)?
      • 简述一下Go语言里的 interface?
      • 简述一下 空接口 (null interface)?空接口的应用?
      • Go语言中 2 个 interface 可以比较吗 ?
      • Go语言中 2 个 stuct 可以比较吗 ?
      • 说几个常用的设计模式?
      • 简单说说 静态代理 和 动态代理 (简单说说 代理模式)?
    • 五、语言类库
      • Go语言中 Context 定义?
      • 简单说一下 Go语言 的 unsafe包?
      • 简单说一下 reflect.DeepEqual函数?
      • 反射 的作用?
    • 六、并发编程(协程)
      • 线程 和 进程 的区别?
      • 协程 (goroutine) 和 线程的区别?
      • 说说 Go语言 中的 协程 (goroutine)?
      • 谈谈你对 CSP并发模型 的理解?
      • 浅谈一下GMP (Go语言的调度模型)?
      • Select 有什么作用?
      • 浅谈一下 Select 机制?
      • 说说 channel 特性?
      • channel 使用场景?
      • 缓冲 channel 和 无缓冲 channel 的区别?
      • nil channel有什么用?
      • channel 如何关闭?
      • 什么情况会导致 Goroutine 发生阻塞?
      • 如果 Goroutine 一直占用资源怎么办,GMP模型怎么处理这个问题?
      • 如何优雅的关闭一个 Goroutine?
      • 谈一下 Go语言中的 WaitGroup?
      • Go语言中有哪几种锁?
      • 说说go语言的 同步锁 (锁)?
      • 谈谈 Go语言 中的 原子操作,和加锁实现有什么区别?
      • 说说你对 Go语言 中 panic 和 recover 的理解?
      • defer语句 的常见使用场景?
      • defer、return 的执行顺序?
      • defer 能否修改 return 的值?
    • 七、IO
    • 八、通信/网络
    • 八、内存分配、垃圾回收
      • 简述一下 Go语言中的 栈内存 (也称调用栈)?
      • 简述一下 Go语言中的 堆内存?
      • 简述 堆 和 栈 有什么区别?
      • 什么是逃逸分析?
      • 逃逸分析的原则 (了解原则后, 尽量写分配到 栈 上的代码)?
      • 简述垃圾回收(GC)方法-三色标记法?
      • 三色标记法的优缺点?
      • Go语言 的 内存模型,为什么小对象多了会造成gc压力。

四、面向对象

如何理解面向对象编程?

  • 1.万物皆对象任何事物 在代码中都可以用 对象 表达出来。
  • 2.对象包含自己的 属性行为。编程的过程:就是创建对象的描述设计对象的行为
  • 3.面向对象编程三大特性 – 封装继承多态。点击查看

Go语言里如何实现面向对象 (实现三大特性)?

点击查看

Go语言 里可以使用 structinterface 实现 面向对象

封装:

type Animal struct {
	name string
}

func (p *Animal) SetName(name string) {
	p.name = name
}

func (p *Animal) GetName() string {
	return p.name
}

继承:

type Animal struct {
	Name string
}

type Cat struct {
	Animal
	FeatureA string
}
type Dog struct {
	Animal
	FeatureB string
}

多态:

type AnimalSounder interface {
 say()
}

func (c *Cat) say() {
	fmt.Println("喵喵喵~")
}

func (c *Dog) say() {
	fmt.Println("汪汪汪·~")
}

// 攻击函数,传入 Animal 对象
func attack(animalSounder AnimalSounder) {
	animalSounder.say()
}

func main() {
	c1 := Cat{}
	d1 := Dog{}
	attack(c1)
	attack(d1)
}

简述一下Go语言里的 interface?

  • Interface类型 是一种 类型,并且是 引用类型
  • Interface类型 可以定义一组 方法,不需要具体实现。(interface 不能包含 变量)
  • interface 的 重要作用 在于实现 多态

简述一下 空接口 (null interface)?空接口的应用?

  • 空接口 (null interface): 没有定义 任何方法的接口 就是 空接口
  • 任何类型 都实现了 空接口, 所以 空接口变量 可以存储 任意类型的值!

空接口 的应用(interface{}):

  • 1.空接口 类型可以作为 函数的参数 (可以接收 任意类型 的参数)。
  • 2.空接口可以作为 mapvalue (可以存储任意值的 map) 。

Go语言中 2 个 interface 可以比较吗 ?

Go 语言 中,interface 可以使用 ==!= 比较。2 个 interface 相等有以下 2 种情况:

  • 两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
  • 类型 T 相同,且对应的值 V 相等。

Go语言中 2 个 stuct 可以比较吗 ?

相同类型的 struct 可以比较,使用 reflect.DeepEqual 方法进行比较。。


说几个常用的设计模式?

点击查看

  • 单例模式: 一个应用程序中,某个类的 实例对象 只能有一个。
  • 观察者模式: 对象间 一对多 的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。(本质就是调用其他对象的函数)
  • 装饰者模式: 对已有的业务逻辑 进一步的封装,使其增加额外的功能。(依赖继承实现, 不断加料)
  • 适配器模式: 把一个类的接口变换成客户端所期待的另一种接口。(创建一个中间类来解决无法调用的问题)

简单说说 静态代理 和 动态代理 (简单说说 代理模式)?

Java代理模式: 静态代理-动态代理 【依赖 接口 实现】

代理模式的优点:(客户-微商代理[代理类]-商家[委托类])

  • 可以隐藏 委托类 的实现;
  • 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

静态代理: 代理类 在程序运行前就已经存在。

  • 缺点: 通过 静态代理 可以在每个方法中都添加 相应的逻辑,假如 Sell接口 中包含上百个方法, 时候使用 静态代理 就会编写许多 冗余代码

动态代理: 代理类 在程序运行时创建。

// 动态代理的关键: 位于代理类与委托类之间的中介类,这个中介类被要求实现 InvocationHandler接口
/**
    当我们调用代理类对象的方法时,这个 "调用" 会转送到invoke方法中,
    代理类对象作为 proxy参数 传入,参数 method 标识了我们具体调用的是代理类的哪个方法,args为这个 方法的参数。
*/
/**
    这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,
    这样我们可以在 invoke方法 中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。
*/

// 调用处理程序
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object
}

// 中介类
public class DynamicProxy implements InvocationHandler { 
    //obj为委托类对象; 
    private Object obj; 
    public DynamicProxy(Object obj) { 
        this.obj = obj; 
    } 
    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        System.out.println("before"); 
        Object result = method.invoke(obj, args); 
        System.out.println("after"); return result; 
    } 
}

五、语言类库

Go语言中 Context 定义?

  • Context 的主要作用是在 Goroutine 之间传递 上下文 信息, 包括: 取消信号超时时间优雅退出Groutine等。
  • 当一个上下文 (Context) 被关闭时,它派生的所有 上下文 也被关闭 (调用的 Groutine 也被 自动关闭)。

简单说一下 Go语言 的 unsafe包?

  • unsafe包 提供了 非类型安全 的指针: unsafe.Pointer
  • 任何类型的 指针unsafe.Pointer 可以 相互转换

简单说一下 reflect.DeepEqual函数?

  • 对于 arrayslicemapstruct等类型,要比较两个值是否相等,使用 == 处理起来十分麻烦,在对效率没有太高要求时,可以使用 reflect 中的 DeepEqual函数

反射 的作用?

  • 通过 TypeOf函数ValueOf函数 获得存储在 interface 中的 类型
  • ValueOf函数 获取的信息通过 Interface() 函数反向转变成 interface变量

六、并发编程(协程)

线程 和 进程 的区别?

  • 进程程序运行资源分配基本单位
  • 线程进程 的一个实体,是 CPU调度 的基本单位,多个 线程 共享 进程 的资源。

协程 (goroutine) 和 线程的区别?

点击查看

  • 线程CPU调度 的基本单位。
  • 协程 可以说是 轻量级线程,多个 协程 共享 线程 的资源, 上下文切换代价小,所以效率更高。

  • 一个 操作系统线程 对应 用户态 多个 goroutine
  • Go程序 可以同时使用多个 OS线程
  • goroutineOS线程多对多 的关系,即 m:n

说说 Go语言 中的 协程 (goroutine)?

  • Go语言 中,在函数前添加 go关键字 即可实现 go的协程
  • Go语言 中通过 channel 来进行 协程 间的通信;

谈谈你对 CSP并发模型 的理解?

点击查看

CSP并发模型 的核心: 不要以 共享内存 的方式来 通信,而是要通过 通信共享内存

  • 【什么是: 以 共享内存 的方式来 通信】: 构建一个 全局共享变量,通过 加锁机制 来保证 共享数据并发环境 下的 线程安全,从而实现并发线程间的 通信
  • 【什么是: 通过 通信共享内存】: 协程 之间通过 channel 进行通信, 协程只需要关注两件事情: 1.往channel中发送数据, 2.从channel中取出数据, 而不用关注另一个 并发实体,这使得并发实体间实现了 完全解耦,这两个并发原语之间没有 从属关系

浅谈一下GMP (Go语言的调度模型)?

点击查看

Goroutine(G) 调度器 POS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行。

  • G ---- goroutine: 即Go协程,每个go关键字都会创建一个协程。
  • M ---- 内核级线程,所有的G都要放在M上才能运行。
  • P ---- 处理器,调度 GM 上,其维护了一个 队列,存储了所有需要它来调度的 G

P (调度器)个数 默认等于 CPU核数,每个 M 必须持有一个 P 才可以执行 G,一般情况下 M 的个数会略大于 P 的个数。


Select 有什么作用?

Go语言 中的 select 的作用就是: 监听 针对 channel读写操作


浅谈一下 Select 机制?

点击查看 — 查看案例

  • 每个 case 都必须是一个 通信
  • 如果 只有一个 通信可以进行,它就 执行
  • 如果有 多个 case 可以运行,则 随机执行 一个。否则执行 default 语句。
  • 如果没有 default 语句,select将阻塞,直到某个 通信 可以运行 (也可以用 select{} 进行 程序阻塞)。

说说 channel 特性?

  • 给一个 nil channel 读写数据,造成永远阻塞
  • 给一个已经 关闭的 channel 发送数据,引起 panic
  • 从一个已经 关闭的 channel 接收数据,如果缓冲区中为空,则返回一个 零值
  • 无缓冲channel同步的,而 有缓冲channel非同步的

channel 使用场景?

点击查看

  • channel 可以用于 协程goroutine 间的 通信
  • 定时任务
  • 超时处理
  • 控制并发数 (协程池 里的 任务队列、结果队列)

缓冲 channel 和 无缓冲 channel 的区别?

  • 无缓冲 channel: channel 发送和接收动作是同时发生的。
    (例如: ch := make(chan int) ,如果没有 接收者 <-ch ,那么 发送者 ch<- 就会 一直阻塞)
  • 缓冲 channel: 类似一个 队列,只有队列满了才可能发生 阻塞

nil channel有什么用?

  • nil channel阻塞 对该 channel 的所有 读写操作
  • 关闭 nil channel (或未初始化的 channel) 会引起 panic

channel 如何关闭?

close(ch1)


什么情况会导致 Goroutine 发生阻塞?

  • 对一个 nil channel 进行 读写操作,造成永远阻塞
  • 死锁。多个协程由于竞争资源导致死锁。

如果 Goroutine 一直占用资源怎么办,GMP模型怎么处理这个问题?

如果有一个 Goroutine 一直占用资源的话,GMP模型 会从 正常模式 转为 饥饿模式,强制让前面的 Goroutine 去分配使用。


如何优雅的关闭一个 Goroutine?

实现接收外部命令退出 Goroutine

  • 使用 channel 实现。
  • 使用 Context 实现 (协程 内调用 协程: 传入 ctx 参数): 【LOOP + ctx.Done()

谈一下 Go语言中的 WaitGroup?

WaitGroup 相当于是一个 计数器,可以用来 记录维护 运行的 goroutine。如果 WaitGroup > 0Wait() 方法就会阻塞。

  • Add(): 设置 WaitGroup计数值,可以理解为 子任务的数量
  • Done(): 将 WaitGroup计数值 减一,可以理解为完成一个子任务
  • Wait(): 用于 阻塞调用者,直到 WaitGroup计数值 为0,即 所有子任务都完成

Go语言中有哪几种锁?

  • Mutex互斥锁
  • RWMutex读写锁RWMutex 基于 Mutex 实现。

说说go语言的 同步锁 (锁)?

  • 1.当一个 协程 获得了 Mutex 后,会阻止其他 协程读写操作
  • 2.RWMutex读锁 占用的情况下,会阻止 ,但不阻止
  • 3.RWMutex写锁 占用情况下,会阻止 读写操作

谈谈 Go语言 中的 原子操作,和加锁实现有什么区别?

Go语言 的原子操作由 atomic 提供。

  • 加锁操作 涉及到 内核态上下文切换,比较耗时,代价高。
  • 原子操作用户态 就可以完成,因此 性能加锁 实现更好。

说说你对 Go语言 中 panic 和 recover 的理解?

点击查看 panic 与 recover 使用案例

  • panic 用来 抛出异常recover 用来 捕获异常处理异常
  • recover 必须配合 defer 使用 (放在 panic 之前)。
  • 程序执行到 panic,会终止执行后面的 代码,但不会阻止 defer程序 的执行。
  • recover 没有传入 参数,但是有 返回值,返回值就是 panic 传递的值。

defer语句 的常见使用场景?

点击查看

  • 通信连接释放
  • 数据库连接释放
  • 文件资源释放

defer、return 的执行顺序?

点击查看

  • 多个 ​​defer​​ 之间的执行顺序是 后进先出
  • return 最先执行,先将结果写入返回值中 (即赋值), 接着执行 defer 语句。

defer 能否修改 return 的值?

点击查看

  • 匿名返回值 (int): 不会影响返回值。
  • 命名返回值 (res int) : 会影响返回值。

七、IO


八、通信/网络


八、内存分配、垃圾回收

面试官:请你详细说说Go的逃逸分析

简述一下 Go语言中的 栈内存 (也称调用栈)?

点击查看

  • 栈内存 往往用来存储 函数参数局部变量函数调用帧,它们 随着 函数 的创建而分配,随着 函数 的退出而释放 (由 编译器 自动进行这些操作)。
  • 一个 goroutine 对应一个

简述一下 Go语言中的 堆内存?

可以理解为:在 GO语言 中的 内存管理 是针对 堆内存 而言的。

  • 不同的是,一个 应用程序 在运行时只会存在 一个
  • 程序运行期间 可以主动从 申请内存,这些内存由 内存分配器 进行分配,并由 垃圾收集器 进行回收。

简述 堆 和 栈 有什么区别?

性能

  • 栈内存 性能好栈内存分配释放 非常高效的。
  • 堆内存 性能差堆内存回收 需要通过 标记清除阶段,例如 三色标记法

加锁

  • 不需要加锁:栈内存 是每个 goroutine 独有的,所以 栈内存 中的 操作 不需要加锁。
  • 有时需要加锁:堆内存 有时需要 加锁 防止 多线程冲突

什么是逃逸分析?

  • 逃逸分析 的基本思想:检查 变量生命周期 是否是完全可知的(如果变量 被外部引用, 则生命周期不可知),如果通过检查,则在 上分配。否则,就是所谓的 逃逸,必须在 上进行分配。
  • 也就是说: 编译器 通过 逃逸分析 技术去选择变量分配在 还是 上。

逃逸分析的原则 (了解原则后, 尽量写分配到 栈 上的代码)?

  • Go的 逃逸分析 是在 编译期 完成的:编译期 无法确定的参数类型 必定放到中;
  • 如果变量在 函数外部存在引用,则必定放在 中, 否则放到
  • 如果变量 占用内存较大 时,则优先放到 中;

简述垃圾回收(GC)方法-三色标记法?

一文搞懂go gc垃圾回收原理

  • 第一步: 新创建的对象, 默认标记为 白色
  • 第二步: GC回收 开始, 从 根节点 遍历所有对象,把遍历到的对象从 白色集合 放入 灰色集合
  • 第三步: 遍历 灰色集合,将 灰色对象 引用的对象从白色集合放入 灰色集合,之后将此 灰色对象 放入 黑色集合
  • 第四步: 重复第三步, 直到灰色集合中无任何对象
  • 第五步: 回收所有的 白色标记 的对象. 也就是回收垃圾

三色标记法的优缺点?

点击查看

优点: 不需要 STW (Stop The World)


缺点:

  • 因为没有 STW, 可能会导致 对象丢失, (在时间间隙内:①"对象A"可能会被"对象2"引用)

Go语言 的 内存模型,为什么小对象多了会造成gc压力。

通常 小对象 过多会导致 GC三色法 消耗过多的 GPU。优化思路是,减少对象分配


你可能感兴趣的:(Golang面试题,golang,面试)