关于 defer - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
lasuar
V2EX    Go 编程语言

关于 defer

  •  
  •   lasuar 2020-04-15 19:32:46 +08:00 3224 次点击
    这是一个创建于 2051 天前的主题,其中的信息可能已经有所发展或是发生改变。

    平时没注意,今天偶然发现子协程 panic 后,主协程的 defer 内的操作不会执行?要说捕捉不到子协程的异常可以理解,请问下有相关的文档说明这个现象的吗?

    func Test_X(t *testing.T) { defer log.Println(111) go func() { panic(222) }() } 

    Out

    === RUN Test_X panic: 222 
    21 条回复    2020-04-17 19:55:41 +08:00
    Mohanson
        1
    Mohanson  
       2020-04-15 19:43:20 +08:00 via Android
    panic 后面加个 sleep,你应该能看到 111
    lasuar
        2
    lasuar  
    OP
       2020-04-15 19:45:07 +08:00
    @Mohanson 不行的,都试过
    yoshiyuki
        3
    yoshiyuki  
       2020-04-15 19:46:57 +08:00
    主协程偶尔会先于子协程结束,因为协程的运行是异步的,主协程并不会去”等待“子协程的运行结束

    要解决这个问题,使用 waitGroup 或者 channel & ...

    note:这不是一个 bug,而是协程以及异步本身的特性
    lasuar
        4
    lasuar  
    OP
       2020-04-15 19:50:08 +08:00
    @yoshiyuki 我知道这不是 bug 。你可以用你说的方式让主协程的 defer 执行,并贴出你的代码。
    ClarkAbe
        5
    ClarkAbe  
       2020-04-15 19:51:35 +08:00 via iPhone
    应该是需要等待狗肉挺执行结束
    lasuar
        6
    lasuar  
    OP
       2020-04-15 19:53:47 +08:00
    贴出一个相同输出的片段:
    ```
    func Test_X(t *testing.T) {
    defer func() {
    log.Println(111)
    }()
    go func() {
    panic(222)
    }()
    time.Sleep(time.Second)
    }
    ```
    thefack
        7
    thefack  
       2020-04-15 19:59:05 +08:00
    多运行几次,你就知道 defer 到底有没有执行过
    hallDrawnel
        8
    hallDrawnel  
       2020-04-15 20:00:18 +08:00   4
    不会,defer 就是这样设计的,只有在调用 defer 的 goroutine 中 panic 才会执行,其他 goroutine 没有提及,那么就是没有保证。
    文档在 https://golang.org/ref/spec#Defer_statements

    A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

    文档指明了在“the corresponding goroutine” panic 的时候才会 defer 。

    为了确定 panic 在一个 goroutine 中的行为,文档也描述了,在 https://golang.org/ref/spec#Handling_panics,我把它贴过来

    While executing a function F, an explicit call to panic or a run-time panic terminates the execution of F. Any functions deferred by F are then executed as usual. Next, any deferred functions run by F's caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking.

    注意到"in the executing goroutine"的限制,那么就是说 panic 会触发在它所运行的 goroutine 中调用链上的 defer,然后整个程序就退出了。所以 panic 不会管其他 goroutine 中的 defer 。
    lasuar
        9
    lasuar  
    OP
       2020-04-15 20:03:54 +08:00
    @hallDrawnel 给力,要的就是这个
    hallDrawnel
        10
    hallDrawnel  
       2020-04-15 20:05:49 +08:00
    话说那个小心心是怎么点出来的??网页端可以吗?
    lasuar
        11
    lasuar  
    OP
       2020-04-15 20:08:01 +08:00
    鼠标移到楼层号左边再左边的位置就会显示了
    lasuar
        12
    lasuar  
    OP
       2020-04-15 20:08:12 +08:00   1
    @hallDrawnel 鼠标移到楼层号左边再左边的位置就会显示了
    yoshiyuki
        13
    yoshiyuki  
       2020-04-15 20:21:02 +08:00
    func main() {
    defer log.Println(111)
    go func() {
    defer func() {
    recover()
    }()
    panic(222)
    }()
    }

    抱歉对你的问题有曲解,如 8#所言,defer 的执行条件是有 return,所以你需要 recover 以保障主协程中 return 的执行
    CEBBCAT
        14
    CEBBCAT  
       2020-04-15 22:07:41 +08:00 via Android
    @yoshiyuki 铁子,可以试用下贴 gist 链接的功能,很棒嗷
    yoshiyuki
        15
    yoshiyuki  
       2020-04-15 22:15:29 +08:00
    @CEBBCAT 谢谢推荐,有操作指南之类的东西吗?
    CEBBCAT
        16
    CEBBCAT  
       2020-04-16 00:12:30 +08:00 via Android
    @yoshiyuki 你也许可以站内搜索一下,我明天也有可能发个教程
    JamesMackerel
        17
    JamesMackerel  
       2020-04-16 10:06:01 +08:00 via iPhone
    go 这种是对称协程,没有老爸儿子的概念。
    davidyanxw
        18
    davidyanxw  
       2020-04-16 14:01:49 +08:00
    defer ... // 压栈
    go func() {panic(222)}() // 子协程

    main 协程和子协程执行顺序未知,都有可能先执行结束
    main 先执行完:会执行 main 协程中的 defer
    子协程先执行完:panic 信息
    lasuar
        19
    lasuar  
    OP
       2020-04-17 09:39:42 +08:00
    @davidyanxw 你测试一下
    davidyanxw
        20
    davidyanxw  
       2020-04-17 18:41:33 +08:00
    @lasuar
    我之前的理解有问题。

    输出结果有几种可能:
    1. main 协程先于子协程结束
    输出:111
    2. 子协程先 panic,main 协程未执行到 println(111)
    输出:panic 222 。。。
    3. 子协程先 panic,main 协程执行到 println(111)
    输出:
    111
    panic 222 。。。

    2/3 是同一个逻辑,panic 忽略了上层的 defer

    结论是:
    1. 8 楼说的,panic 只处理当前协程的 defer 函数
    2. panic 向上抛出
    CEBBCAT
        21
    CEBBCAT  
       2020-04-17 19:55:41 +08:00   1
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     976 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 19:27 PVG 03:27 LAX 11:27 JFK 14:27
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86