Golang中如何在循环中捕获panic并继续执行

Golang中如何在循环中捕获panic并继续执行 各位专家好,从我的问题状态可以看出,我是Go语言的新手,之前有Python背景。我的实际案例比较复杂,但为了清晰阐述问题,这里使用一个从0到10的简单循环示例。我试图实现当循环变量i等于2时触发panic,通过defer机制进行恢复,然后让循环继续执行。

期望的输出结果应该是这样的:

0
1
panic occured: got 2
3
4
.
.
.
.
.
10

实际得到的输出却是:

0
1
panic occured got:  got 2

代码实现:

package main

import "fmt"

func main() {
	goFrom1To10()
}

func goFrom1To10() {
	defer recovery()
	for i := 0; i <= 10; i++ {

		if i == 2 {
			panic("got 2")

		}
		fmt.Println(i)
	}

}

func recovery() {
	if r := recover(); r != nil {
		fmt.Println("panic occured: ", r)
	}

}

虽然这种在恢复后继续循环的处理方式可能看起来不太常规,但在我的实际场景中,遇到panic后必须继续执行循环是必要的需求。

谢谢。


更多关于Golang中如何在循环中捕获panic并继续执行的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

非常感谢您通过示例进行的清晰解释。现在我能理解,defer 应该放在函数内部,以避免中断 for 循环。

更多关于Golang中如何在循环中捕获panic并继续执行的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


即使进行恢复,函数仍会立即结束。你可以在for循环中使用类似这样的函数:

package main

import "fmt"

func main() {
	goFrom1To10()
}

func goFrom1To10() {
	for i := 0; i <= 10; i++ {
		func() {
			defer recovery()
			if i == 2 {
				panic("got 2")
			}
			fmt.Println(i)
		}()
	}
}

func recovery() {
	if r := recover(); r != nil {
		fmt.Println("panic occured: ", r)
	}
}

这样做的好处是,在匿名函数中延迟执行恢复。如果发生panic,它只会跳出该函数,仍然会正常继续for循环。 请记住,panic并不是很优雅的做法,在可能的情况下应该尽量避免使用。

在你的代码中,defer recovery() 被放置在 goFrom1To10() 函数的开头,这意味着 recovery() 只会在函数退出时执行一次。当 panic 在 i=2 时触发后,recovery() 确实会捕获并恢复 panic,但此时循环已经被中断,函数直接返回,因此无法继续执行剩余的迭代。

要实现 panic 恢复后继续循环,需要在循环内部处理 panic 恢复。以下是两种可行的实现方式:

方案一:在循环内部使用 defer(推荐)

package main

import "fmt"

func main() {
    for i := 0; i <= 10; i++ {
        func() {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Printf("panic occured: %v\n", r)
                }
            }()
            
            if i == 2 {
                panic("got 2")
            }
            fmt.Println(i)
        }()
    }
}

方案二:封装循环逻辑到独立函数

package main

import "fmt"

func main() {
    for i := 0; i <= 10; i++ {
        safeExecute(i)
    }
}

func safeExecute(i int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("panic occured: %v\n", r)
        }
    }()
    
    if i == 2 {
        panic("got 2")
    }
    fmt.Println(i)
}

方案三:使用通用的安全执行器

package main

import "fmt"

func main() {
    for i := 0; i <= 10; i++ {
        safeRun(func() {
            if i == 2 {
                panic("got 2")
            }
            fmt.Println(i)
        })
    }
}

func safeRun(f func()) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("panic occured: %v\n", r)
        }
    }()
    f()
}

以上三种方案都能产生你期望的输出:

0
1
panic occured: got 2
3
4
5
6
7
8
9
10

核心原理是在每次循环迭代中创建一个新的作用域,这样每次迭代的 panic 恢复都是独立的,不会影响后续的循环执行。方案一最为简洁,直接在循环内部使用匿名函数和 defer;方案二和方案三提供了更好的代码组织和复用性。

回到顶部