golang面试题《并发编程》篇

480 阅读7分钟

欢迎收听《面试速通》,这是一个专注于帮助求职者快速掌握面试技巧和知识的播客节目。在本期节目中,我们将探讨Go语言中的并发编程,这些问题在Go语言的技术面试中经常出现。无论你是初学者还是有一定编程经验的开发者,希望这些内容能帮助你更好地理解和使用Go语言的并发特性。

1. 什么是Go语言的goroutine?如何创建和使用goroutine?

  • goroutine:Go语言中的轻量级线程,由Go运行时管理。它使得实现并发变得简单高效。
  • 创建和使用:使用go关键字创建,例如go func() { ... }()

2. 什么是Go语言的通道(channel)?如何使用通道进行通信?

  • 通道(channel) :用于在goroutine之间传递数据,提供一种类型安全的通信机制。
  • 使用:通过make创建通道,然后使用<-操作符发送和接收数据。

3. 解释Go语言中的select语句及其使用场景。

  • select语句:用于多路复用channel操作。可以等待多个channel中的任意一个操作完成。
  • 使用场景:实现超时控制、处理多个channel的并发操作、实现非阻塞的channel操作。

4. 什么是Go语言的sync包?有哪些常用的同步原语?

  • sync包:提供基本的同步原语。
  • 常用同步原语:包括Mutex(互斥锁)、RWMutex(读写锁)、WaitGroup(等待组)、Cond(条件变量)和Once(只执行一次)。

5. 如何在Go语言中避免数据竞争(race condition)?

  • 避免数据竞争

    • 使用同步原语如sync.Mutex保护共享数据。
    • 使用channel传递数据以避免多个goroutine同时访问同一数据。

6. 什么是 GMP?

  • GMP:Go运行时管理goroutine的调度模型,包括G(goroutine)、M(操作系统线程)和P(处理器)。

    • G:代表goroutine。
    • M:代表操作系统线程。
    • P:代表逻辑处理器,负责分发goroutine到M上执行。

7. 进程、线程、协程有什么区别?

  • 进程:独立的程序运行实体,拥有独立的内存空间。
  • 线程:进程中的执行单元,共享进程的内存空间。
  • 协程:比线程更轻量级,不依赖于操作系统内核,由用户态调度。

8. 抢占式调度是如何抢占的?

  • 抢占式调度:操作系统或运行时系统在某些条件下强制中断正在运行的任务,以切换到其他任务。Go运行时会在函数调用、系统调用等点进行抢占。

9. M 和 P 的数量问题?

  • 数量问题:默认情况下,P的数量等于CPU核心数,可以通过runtime.GOMAXPROCS设置。M的数量取决于系统资源和goroutine的数量。

10. 除了 mutex 以外还有那些方式安全读写共享变量?

  • 其他方式:使用channel进行数据传递,sync/atomic包提供的原子操作。

11. Go 如何实现原子操作?

  • 原子操作:通过sync/atomic包提供的原子函数,如atomic.AddInt32atomic.LoadInt32等。

12. Mutex 是悲观锁还是乐观锁?悲观锁、乐观锁是什么?

  • Mutex:悲观锁。

    • 悲观锁:假设操作会发生冲突,每次操作前都加锁。
    • 乐观锁:假设操作不会发生冲突,操作前不加锁,操作后检查冲突。

13. Mutex 有几种模式?

  • 模式:主要有两种模式:

    • 普通模式:互斥锁,确保同时只有一个goroutine可以访问共享资源。
    • 递归模式:Go语言中不支持递归锁。

14. goroutine 的自旋占用资源如何解决?

  • 解决方法:使用适当的同步机制(如sync.Mutex、channel)避免自旋,合理设计调度策略。

15. 怎么控制并发数?

  • 控制并发数:使用带缓冲的channel或sync.WaitGroup限制并发goroutine的数量。

16. 多个 goroutine 对同一个 map 写会 panic,异常是否可以用 defer 捕获?

  • 会panic:多个goroutine同时写map会导致panic。
  • defer捕获:可以使用recover在defer中捕获panic,但这不是解决数据竞争的正确方法。

17. 如何优雅的实现一个 goroutine 池?

  • 实现方法:使用带缓冲的channel作为任务队列,worker从队列中获取任务并执行。

18. 什么是channel,为什么它可以做到线程安全?

  • channel:用于goroutine之间通信的类型安全管道。
  • 线程安全:Go运行时通过锁和条件变量等机制保证channel的并发安全。

19. Channel是同步的还是异步的?

  • 同步和异步:无缓冲channel是同步的,有缓冲channel是异步的。

20. 向 channel 发送数据和从 channel 读数据的流程是什么样的?

  • 发送数据流程:发送goroutine将数据写入channel,如果channel已满则阻塞。
  • 读取数据流程:接收goroutine从channel读取数据,如果channel为空则阻塞。

21. 讲讲 Go 的 chan 底层数据结构和主要使用场景?

  • 底层数据结构:chan底层是一个循环队列,包含锁和条件变量。
  • 主要使用场景:goroutine间通信、任务分发、同步控制等。

22. Channel 分配在栈上还是堆上?哪些对象分配在堆上,哪些对象分配在栈上?

  • 分配位置:channel通常分配在堆上。
  • 分配规则:局部变量可能分配在栈上,逃逸分析确定的变量分配在堆上。

23. nil、关闭的 channel、有数据的 channel,再进行读、写、关闭会怎么样?

  • nil channel:读写操作会永久阻塞。
  • 关闭的 channel:写操作会panic,读操作会返回零值。
  • 有数据的 channel:读操作返回数据,写操作可能阻塞。

24. 对已经关闭的的 chan 进行读写,会怎么样?为什么?

  • 读操作:返回零值。
  • 写操作:会panic。因为channel关闭后不能再写入数据。

25. for循环select时,如果通道已经关闭会怎么样?如果select中的case只有一个,又会怎么样?

  • 通道关闭:select会选择通道关闭的case。
  • 只有一个case:select会一直等待该case满足条件。

26. goroutine 和 KernelThread 之间是什么关系

  • 关系:goroutine是用户态线程,Go运行时将goroutine调度到操作系统的KernelThread上执行。

27. 为何GPM调度要有P

  • 原因:P(处理器)用于管理goroutine队列和执行上下文,保证goroutine的高效调度和执行。

28. 如何在goroutine执行一半就退出协程

  • 退出协程:使用context取消机制或channel通知机制,在goroutine中检查取消信号。

29. Goroutine和Channel的作用分别是什么

  • goroutine:实现并发执行,轻量级线程。
  • channel:用于goroutine之间的通信和同步。

30. 怎么查看Goroutine的数量

  • 查看数量:使用runtime.NumGoroutine()函数。

31. Goroutine和线程的区别

  • 区别

    • goroutine是由Go运行时管理的轻量级线程,比系统线程更轻量。
    • goroutine调度由Go运行时完成,线程调度由操作系统完成。

32. Go主协程如何等其余协程完再操作

  • 等待方法:使用sync.WaitGroup等待所有goroutine完成。

33. goroutine 发生了泄漏如何检测

  • 检测方法:使用工具如pprofgo tool trace或第三方工具监控goroutine的数量和状态。

34. Goroutine的自旋占用资源如何解决?

  • 解决方法:使用适当的同步机制(如sync.Mutex、channel)避免自旋,合理设计调度策略。

35. 怎么控制并发数?

  • 控制并发数:使用带缓冲的channel或sync.WaitGroup限制并发goroutine的数量。

感谢收听本期《面试速通》。希望这些关于Go语言并发编程的面试问题和解答对你有所帮助。记得关注我们的节目,获取更多面试技巧和知识。我们,下期再见!