欢迎收听《面试速通》,这是一个专注于帮助求职者快速掌握面试技巧和知识的播客节目。在本期节目中,我们将探讨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.AddInt32、atomic.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 发生了泄漏如何检测
- 检测方法:使用工具如
pprof、go tool trace或第三方工具监控goroutine的数量和状态。
34. Goroutine的自旋占用资源如何解决?
- 解决方法:使用适当的同步机制(如
sync.Mutex、channel)避免自旋,合理设计调度策略。
35. 怎么控制并发数?
- 控制并发数:使用带缓冲的channel或
sync.WaitGroup限制并发goroutine的数量。
感谢收听本期《面试速通》。希望这些关于Go语言并发编程的面试问题和解答对你有所帮助。记得关注我们的节目,获取更多面试技巧和知识。我们,下期再见!