如何在父 goroutine 中通过超时控制来结束子 goroutine?

我先说下我对 context.WithTimeout 的用法的一点理解,不正之处请指出: 在子 goroutine 中通过一个for循环来不断判断 context 是否超时,超时则return,否则执行默认操作,代码如下:

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    var wg sync.WaitGroup
    wg.Add(1)

    go func(ctx context.Context) {
        defer wg.Done()
        for {
            select {
            case <-ctx.Done():
                fmt.Println(ctx.Err()) // prints "context deadline exceeded"
                return
            default:
                time.Sleep(1 * time.Second)
                fmt.Println("Done after 1 seconds")
            }
        }
    }(ctx)
    wg.Wait()
}

执行结果如下:

Done after 1 seconds
Done after 1 seconds
Done after 1 seconds
Done after 1 seconds
Done after 1 seconds
context deadline exceeded

但是,如果在一个子 goroutine 中执行一个时间超过超时控制的任务,那又该如何返回呢? 比如:

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    var wg sync.WaitGroup
    wg.Add(1)

    go func(ctx context.Context) {
        defer wg.Done()
        select {
        case <-ctx.Done():
            fmt.Println(ctx.Err()) // prints "context deadline exceeded"
            return
        default:
            time.Sleep(10 * time.Second)
            fmt.Println("Done after 10 seconds")
        }
    }(ctx)
    wg.Wait()
}

执行结果如下:

Done after 10 seconds

当然可以在父 goroutine 中加一个channel,如果 一定时间后无法从 channel 中取值,则忽略子 goroutine,直接继续其它操作。但这种情况下,子 goroutine 并未被关闭。假如这个 goroutine 中执行的任务非常消耗CPU,那么就浪费了计算资源。

所以,这种情况下应该如何关闭子 goroutine 呢?

已邀请:

h12 - https://h12.io

赞同来自: yet

超时要用time.After,怎么能用Sleep山寨呢?

chai2010 - 数盲患者

赞同来自: DennisMao

如果是一个cpu型的死循环,只要霸占了cpu就是无法剥夺了,基本可以认为是cpu核泄露了

panxue - 90后IT男

赞同来自:

体会一下区别

package main

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    defer cancel()
    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        defer wg.Done()
        select {
        case <-ctx.Done():
            fmt.Println(ctx.Err()) // prints "context deadline exceeded"
            return
        case <- time.NewTicker(10 * time.Second).C:
            //time.Sleep(10 * time.Second)
            fmt.Println("Done after 10 seconds")
        }
    }()
    wg.Wait()
}

leaxoy - 93后

赞同来自:

和一楼的对比一下,了解一下select的规则

panxue - 90后IT男

赞同来自:

我想我明白楼主想问下什么, 有两个方案. 第一个, 在子 goroutine在运行一个子 goroutine, 使用ctx的超时+panic, 强制中断程序 第二个, 把子 goroutine拆分任务, 拆成一个个不可中断的操作, 在每一个不可中断的操作之前判断当前是否超时.

我暂时没有想到其他方案.

myonlyzzy

赞同来自:

你这个问题的根本不是在超时还是什么.可以这样想,如果time.sleep 是一条 io 阻塞的操作函数,如果一直阻塞,下面的println永远不会被执行,函数永远执行不到return.

ddxx11223

赞同来自:

可以在select中再加一个time.After来设置一个二级超时,如果超过这个超时,则return,goroutine自然而然就退出了

myonlyzzy

赞同来自:

一点基本概念 1.goroutine无法从外部被关闭,除了结束进程 2.父goroutine和它创建的goroutine是平行关系,不是父goroutine退出了,子goroutine就会退出。 3.select 中如果有default就会执行一遍就结束,如果没有就相当于一个for循环直到某个case为ture.

  1. context其实也是通过channel来实现的.

georgetso

赞同来自:

自己体会一下:

package main

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

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    var wg sync.WaitGroup
    wg.Add(1)

    go func(ctx context.Context) {
        defer wg.Done()

        ch := make(chan bool)

        go func() {
            time.Sleep(10 * time.Second)
            ch <- true
        }()

        select {
        case <-ctx.Done():
            fmt.Println(ctx.Err()) // prints "context deadline exceeded"
        case <-ch:
            fmt.Println("Done after 10 seconds")
        }
    }(ctx)
    wg.Wait()
}

要回复问题请先登录注册