请教 context.WithTimeout 的正确用法

nfwater · 2020-11-29 17:15:23 · 3904 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2020-11-29 17:15:23 的主题,其中的信息可能已经有所发展或是发生改变。

如下这段代码:

package main

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

var wg sync.WaitGroup

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    wg.Add(2)
    go watch(ctx, 1)
    go watch(ctx, 2)
    wg.Wait()

    select {
    case <-ctx.Done():
        fmt.Printf("watch %d %s\n", 0, ctx.Err())
    }

    fmt.Println("finished")
}

func watch(ctx context.Context, flag int) {
    defer wg.Done()

    func() {
        fmt.Printf("doing something flag:%d\n", flag)
        time.Sleep(50 * time.Second)
        fmt.Println("finished flag:", flag)
    }()
}

在 main 里创建了一个 3s 超时的 ctx,并“捕获”完成信号,把 ctx 传到两个协程里,协程里 sleep 50s 来模拟工作,为什么到了 3s ,协程都还没有退出呢?

请教一下这个例子应该怎么改?谢谢!


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3904 次点击  
加入收藏 微博
5 回复  |  直到 2020-12-14 00:19:36
nfwater
nfwater · #1 · 4年之前

这样是可以的,但感觉不像是标准用法

package main

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

var wg sync.WaitGroup

// main 设置一个 3s 超时的 ctx,传给每个 goroutine 第一个参数,每个 goroutine 又发起协程处理任务,
// 假设需要 5s,通过监听 ctx.Done 判断是否超时退出
func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    wg.Add(2)
    go watch(ctx, 1)
    go watch(ctx, 2)
    wg.Wait()

    fmt.Println("finished")
}

func watch(ctx context.Context, flag int) {
    defer wg.Done()

    go func() {
        fmt.Printf("doing something flag:%d\n", flag)
        time.Sleep(50 * time.Second)
        fmt.Println("finished flag:", flag)
    }()

    select {
    case <-ctx.Done():
        fmt.Printf("watch %d %s\n", flag, ctx.Err())
        return
    }
}
colinrs
colinrs · #2 · 4年之前
package main

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

var wg sync.WaitGroup

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    go func() {
        wg.Add(2)
        go watch(ctx, 1)
        go watch(ctx, 2)
        wg.Wait()
    }()

    select {
    case <-ctx.Done():
        fmt.Printf("watch %d %s\n", 0, ctx.Err())
    }

    fmt.Println("finished")
}

func watch(ctx context.Context, flag int) {
    defer wg.Done()

    func() {
        fmt.Printf("doing something flag:%d\n", flag)
        time.Sleep(50 * time.Second)
        fmt.Println("finished flag:", flag)
    }()
}
  • 以上改法就可以实现你说的退出
  • 你的写法程序会在 wg.wait() 上停住,等待两个协程结束,所以你的写法不会出现超时退出的情况
nfwater
nfwater · #3 · 4年之前

更正

nfwater
nfwater · #4 · 4年之前

更正

nfwater
nfwater · #5 · 4年之前
colinrscolinrs #2 回复

```go package main import ( "context" "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() go func() { wg.Add(2) go watch(ctx, 1) go watch(ctx, 2) wg.Wait() }() select { case <-ctx.Done(): fmt.Printf("watch %d %s\n", 0, ctx.Err()) } fmt.Println("finished") } func watch(ctx context.Context, flag int) { defer wg.Done() func() { fmt.Printf("doing something flag:%d\n", flag) time.Sleep(50 * time.Second) fmt.Println("finished flag:", flag) }() } ``` * 以上改法就可以实现你说的退出 * 你的写法程序会在 wg.wait() 上停住,等待两个协程结束,所以你的写法不会出现超时退出的情况

有个问题:如果 watch 在超时时间 3s 内执行完,主协程仍要最低等待 3s,如何进一步优化?

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传