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

一段 Go 代码执行疑问,关于 defer 执行时机的问题

  •  
  •   CarrieBauch · 2023-06-30 11:22:28 +08:00 · 1328 次点击
    这是一个创建于 505 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问一个 Go 程序的执行问题

    下面的代码中,我虽然知道了 1s 之后,会执行 case <-ctx.Done() 这个 case 。但是我想搞清楚原理:

    我有一个疑问:

    1. defer cancel() 是需要等到 main 函数返回才可以执行
    2. 从程序代码中看,下面的程序中,执行 select 的时候,都会阻塞住才对
    3. 如果 select 都会阻塞住的话,那就没有办法执行 return 了,如果没有执行 return 的话,那就不应该执行 defer cancel() 才对
    package main
    
    import (
    	"context"
    	"fmt"
    	"time"
    )
    
    const shortDuration = 1 * time.Second
    
    func main() {
    	timeNow := time.Now()
    	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    	defer cancel()
    	select {
    	case <-time.After(2 * time.Second):
    		fmt.Println("overslept")
    	case <-ctx.Done():
    		fmt.Println(ctx.Err())
    	}
    	fmt.Println(time.Since(timeNow))
    
    }
    
    
    第 1 条附言  ·  2023-06-30 12:06:46 +08:00

    结贴了,执行 case <-ctx.Done() 是因为时间到了,而不是 defer cancel() 被执行了

    程序改为下面这样子,就清晰很多了,可以看到,就算没有执行 cancel(),也会执行 case <-ctx.Done()

    package main
    
    import (
    	"context"
    	"fmt"
    	"time"
    )
    
    const shortDuration = 1 * time.Second
    
    func main() {
    	timeNow := time.Now()
    	ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    	//defer cancel()
    	defer func() {
    		time.Sleep(5 * time.Second)
    		fmt.Println(1111)
    		cancel()
    	}()
    	select {
    	case <-time.After(2 * time.Second):
    		fmt.Println("overslept")
    	case <-ctx.Done():
    		fmt.Println(ctx.Err())
    	}
    	fmt.Println(time.Since(timeNow))
    
    }
    
    
    6 条回复    2023-06-30 12:01:14 +08:00
    9313841411
        1
    9313841411  
       2023-06-30 11:31:51 +08:00
    1s 后
    case <-ctx.Done():
    fmt.Println(ctx.Err())
    会被执行 select 执行完成 -> main 函数退出
    zeonluang
        2
    zeonluang  
       2023-06-30 11:34:15 +08:00
    select 在 case <-ctx.Done():这行就满足条件了吧,然后 return 了。
    return 之后再调用了 cancel()
    mcfog
        3
    mcfog  
       2023-06-30 11:35:41 +08:00
    想说看文档结果发现这段程序就是文档,那你继续看一下 WithTimeout 和关联的 WithDeadline 的文档里的描述,这里并没有什么原理,就只是文档描述的行为

    另外看你的描述,如果你认为代码的执行顺序是 1 秒后执行了 cancel(),然后 select 进第二个 case ,我建议你把 defer cancel()这部分改成
    defer func(){
    LOGSOMETHING;
    cancel()
    }()
    确认一下执行顺序,再看一下文档理解一下
    Trim21
        4
    Trim21  
       2023-06-30 11:38:09 +08:00
    这里的 context.WithTimeout 会启动一个 goroutine ,在 1s 之后 canal 掉这个 ctx

    这里的 select 无论如何都不会阻塞,无论是 1s 之后的 ctx.Done() 还是 2s 之后这个 timer 会返回,都会让这个 select 继续运行...
    CarrieBauch
        5
    CarrieBauch  
    OP
       2023-06-30 11:58:25 +08:00
    @mcfog
    谢谢你,程序改为下面的这样的,就清晰很多了。

    结论就是你说的,不是 defer cancel() 让 -ctx.Done()。而是时间到了之后,ctx.Done 里面就会有消息写入了。

    defer cancel() 其实就是一个兜底的策略,可以取保 main 返回的时候,可以 cancel 掉

    ```
    package main

    import (
    "context"
    "fmt"
    "time"
    )

    const shortDuration = 1 * time.Second

    func main() {
    timeNow := time.Now()
    ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
    //defer cancel()
    defer func() {
    time.Sleep(5 * time.Second)
    fmt.Println(1111)
    cancel()
    }()
    select {
    case <-time.After(2 * time.Second):
    fmt.Println("overslept")
    case <-ctx.Done():
    fmt.Println(ctx.Err())
    }
    fmt.Println(time.Since(timeNow))

    }

    ```
    CarrieBauch
        6
    CarrieBauch  
    OP
       2023-06-30 12:01:14 +08:00
    咦,玩 V 站不久,回复里面竟然无法使用 markdown 格式
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2896 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 03:30 · PVG 11:30 · LAX 19:30 · JFK 22:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.