groutine并发相关
1、怎么控制并发数? 2、多个 goroutine 对同一个 map 写会 panic,异常是否可以用 defer 捕获? 3、如何优雅的实现一个 goroutine 池(百度、手写代码)
Goroutines 是 Go 语言并发编程的核心概念,Go 语言面试中经常涉及到与 Goroutines 相关的问题。以下是一些典型的 Goroutines 相关的面试题,涵盖了从基础到进阶的各个方面。
# 1. Goroutine 的基本概念
- 问题: 请解释什么是 Goroutine,它与传统的线程有什么区别?
- 回答要点:
- Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时管理。
- 相较于操作系统线程,Goroutine 占用更少的内存,启动和销毁的开销更小。
- Go 运行时负责调度 Goroutine,使其高效运行在多核处理器上。
# 2. 如何启动一个 Goroutine?
- 问题: 请写出启动一个 Goroutine 的代码示例,并解释 Goroutine 的启动方式。
- 回答要点:
go func() { fmt.Println("Hello, Goroutine") }()
1
2
3- 使用
go
关键字来启动一个 Goroutine,后跟一个函数调用。 - 启动 Goroutine 后,主函数将继续执行,而 Goroutine 运行在后台。
- 使用
# 3. Goroutine 的调度模型(GMP 模型)
- 问题: 请解释 Go 语言中的 Goroutine 调度模型。
- 回答要点:
- Go 的 Goroutine 调度采用 GMP 模型,即 Goroutine(G)、操作系统线程(M)、调度器(P)三者的结合。
- G 表示 Goroutine,M 表示操作系统线程,P 表示调度器的处理器。
- 调度器通过 P 将 G 分配给 M 执行,P 控制 M 执行 Goroutine 的上下文切换。
- GMP 模型使得 Goroutine 能高效运行在多核处理器上,提供并发能力。
# 4. Goroutine 泄漏
- 问题: 什么是 Goroutine 泄漏?如何避免?
- 回答要点:
- Goroutine 泄漏是指 Goroutine 无法正常退出,导致其持续占用资源。
- 可能的原因包括:Goroutine 阻塞在无法完成的 I/O 操作、无穷循环、等待无法接收的数据等。
- 避免 Goroutine 泄漏的方法:
- 使用
context
控制 Goroutine 的生命周期。 - 确保 Goroutine 有明确的退出条件。
- 定期监控应用中的 Goroutine 数量。
- 使用
# 5. Goroutine 和 Channel 的关系
- 问题: 如何通过 Channel 实现 Goroutine 之间的通信?
- 回答要点:
ch := make(chan int) go func() { ch <- 42 // 发送数据到 channel }() value := <-ch // 从 channel 接收数据 fmt.Println(value)
1
2
3
4
5
6
7
8- Channel 是 Goroutine 之间进行通信的管道,可以用于同步 Goroutine。
- 通过 Channel 可以在 Goroutine 之间传递数据,实现安全的并发操作。
# 6. Goroutine 的并发与同步
- 问题: 在多个 Goroutine 并发执行时,如何确保数据安全?
- 回答要点:
- 使用 Channel 来传递数据和控制并发。
- 使用
sync.Mutex
或sync.RWMutex
实现对共享数据的互斥锁定。 - 使用
sync.WaitGroup
来等待一组 Goroutine 完成。
# 7. Goroutine 的数量控制
- 问题: 如何控制应用中 Goroutine 的数量,防止创建过多的 Goroutine?
- 回答要点:
- 使用带缓冲的 Channel 控制并发 Goroutine 的数量:
ch := make(chan struct{}, 10) // 限制同时运行的 Goroutine 数量为 10 for i := 0; i < 100; i++ { ch <- struct{}{} // 占用一个 Goroutine 配额 go func(i int) { defer func() { <-ch }() // 释放 Goroutine 配额 fmt.Println(i) }(i) }
1
2
3
4
5
6
7
8
9 - 使用
sync.Pool
来复用 Goroutine,减少 Goroutine 的创建和销毁次数。
- 使用带缓冲的 Channel 控制并发 Goroutine 的数量:
# 8. Goroutine 的启动顺序与执行顺序
- 问题: 启动多个 Goroutine 后,能否保证它们的执行顺序?如何控制它们的执行顺序?
- 回答要点:
- Goroutine 的执行顺序不能被保证,因为 Goroutine 的调度由 Go 运行时决定。
- 如果需要控制 Goroutine 的执行顺序,可以通过 Channel 或
sync.WaitGroup
等同步机制来实现:var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() fmt.Println("Goroutine 1") }() go func() { defer wg.Done() fmt.Println("Goroutine 2") }() wg.Wait() // 等待两个 Goroutine 都执行完
1
2
3
4
5
6
7
8
9
10
11
12
# 9. go func
闭包陷阱
- 问题: 在 Goroutine 中使用
for
循环启动多个 Goroutine,为什么所有 Goroutine 打印的都是同一个值? - 回答要点:
- 这是因为 Goroutine 中的闭包捕获了循环变量的引用,而不是值。所有的 Goroutine 会共享同一个循环变量。
- 解决方法是显式传递变量的副本:
for i := 0; i < 5; i++ { go func(i int) { fmt.Println(i) }(i) // 传递 i 的副本 }
1
2
3
4
5
在 Go 中,当你在一个 for
循环内启动多个 Goroutine,通常会遇到所有 Goroutine 打印相同的值,这个问题通常与闭包的变量捕获方式有关。
具体来说,for
循环中的变量(例如循环变量 i
)是被闭包捕获的,这意味着所有 Goroutine 都持有对该变量的引用,而不是在每次循环时创建一个新的副本。因此,当所有 Goroutine 启动时,可能 for
循环已经结束,循环变量 i
的值是最后一次迭代后的值。
为了修复这个问题,可以将循环变量作为参数传递给一个新的将要执行的函数,确保每个 Goroutine 都拥有一个独立的变量副本。你可以使用一个函数内的局部变量来实现这一点。以下是示例代码:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
}(i) // 将 i 作为参数传递给 Goroutine
}
// 等待一会儿确保所有 Goroutine 有时间执行(这里只是为了演示,通常不推荐这样做)
time.Sleep(1 * time.Second)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在这个示例中,每个 Goroutine 都接收了自己独立的 i
的副本,这样每个 Goroutine 在运行时打印的就是自己对应的值,而不会打印同一个值。
# 10. Goroutine 的调度与抢占
- 问题: Go 语言如何实现 Goroutine 的调度?Goroutine 是否会被抢占?
- 回答要点:
- Go 语言使用 GMP 模型进行 Goroutine 的调度。
- Goroutine 不会主动被抢占,而是通过特定的点(如函数调用、Channel 操作)触发调度。
- Go 1.14 引入了 Goroutine 的抢占式调度机制,可以在 Goroutine 长时间运行时强制中断其执行,让其他 Goroutine 获得运行机会。
# 11. Goroutine 的启动和栈空间
- 问题: 启动一个 Goroutine 所需的栈空间有多大?这个栈空间是固定的吗?
- 回答要点:
- Goroutine 的初始栈空间很小,通常只有 2KB。
- Goroutine 的栈空间是动态增长的,可以根据需要自动扩展和收缩。
- 这种动态栈机制是 Go 语言能够高效管理大量 Goroutine 的关键之一。
# 12. Goroutine 的最佳实践
- 问题: 使用 Goroutine 时有哪些最佳实践?
- 回答要点:
- 确保 Goroutine 能够及时退出,避免 Goroutine 泄漏。
- 在并发访问共享数据时使用适当的同步机制,如 Channel、Mutex 等。
- 使用
context
控制 Goroutine 的生命周期和取消操作。 - 监控应用中的 Goroutine 数量,避免过度创建。
- 通过
recover
捕获 Goroutine 中的 panic,避免整个程序崩溃。
这些问题从 Goroutine 的基础概念到复杂的并发控制,能够有效考察候选人对 Go 语言并发编程的理解和实际应用能力。在面试中,对这些问题的深入理解和实践经验将帮助你更好地展示自己的技术水平。
编辑 (opens new window)
上次更新: 2024/09/13, 11:59:12