1. panic
与 Goroutine 调度器的交互
- 单个 Goroutine 内的
panic
:当一个 Goroutine 发生 panic
时,该 Goroutine 会立即停止正常执行流程。它会开始展开(unwind)调用栈,依次执行该 Goroutine 中已注册的 defer
语句。在这个过程中,该 Goroutine 不会被调度器调度去执行其他代码,因为它已经进入了异常处理流程。
- 对其他 Goroutine 的影响:默认情况下,一个 Goroutine 发生
panic
不会直接影响其他 Goroutine。调度器会继续正常调度其他活跃的 Goroutine。但是,如果没有在当前 Goroutine 中使用 recover
来捕获 panic
,当这个 Goroutine 展开调用栈完毕后,整个程序将会崩溃,所有其他 Goroutine 也会随之结束。
2. defer
语句与调度器的关系
- 执行顺序:在并发环境下,
defer
语句的执行顺序依然遵循后进先出(LIFO)原则,与调度器无关。当一个 Goroutine 执行到 defer
语句时,它会将 defer
后的函数调用压入一个栈结构中。当该 Goroutine 正常结束或者发生 panic
开始展开调用栈时,这些 defer
函数会按照 LIFO 顺序依次执行。调度器在调度 Goroutine 时,不会干扰 defer
语句在单个 Goroutine 内的这种执行顺序。
3. 高并发场景下的优化策略
- 使用
recover
捕获 panic
:在每个可能出现错误导致 panic
的 Goroutine 中,使用 recover
来捕获 panic
,避免程序崩溃。例如:
func worker() {
defer func() {
if r := recover(); r != nil {
// 处理 panic,记录日志等
log.Printf("Recovered from panic: %v", r)
}
}()
// 可能会 panic 的代码
}
- 错误传递与处理:避免在 Goroutine 中直接
panic
,而是通过返回错误值的方式将错误传递出来处理。例如:
func worker() error {
// 执行任务
if err := doSomething(); err != nil {
return err
}
return nil
}
- 限制并发数量:使用
sync.WaitGroup
和 channel
来限制并发执行的 Goroutine 数量,防止因大量并发导致系统资源耗尽。例如:
func main() {
var wg sync.WaitGroup
semaphore := make(chan struct{}, 10) // 最多允许 10 个 Goroutine 并发
for i := 0; i < 100; i++ {
semaphore <- struct{}{}
wg.Add(1)
go func() {
defer func() {
<-semaphore
wg.Done()
}()
// 执行任务
}()
}
wg.Wait()
}
- 优化共享资源访问:如果多个 Goroutine 访问共享资源,使用
sync.Mutex
或 sync.RWMutex
等同步机制来保证数据一致性,减少因资源竞争导致的错误和性能问题。例如:
var mu sync.Mutex
var data int
func updateData() {
mu.Lock()
data++
mu.Unlock()
}