美文网首页
golang context 看这篇就够啦

golang context 看这篇就够啦

作者: 护念 | 来源:发表于2023-11-17 17:02 被阅读0次

    context

    context字面意思是上下文,它有什么用呢?
    它主要用于多gorountine、多层级的goroutine(一个goroutine下又有goroutine-子goroutine)的情况下,上层对于开启的gorotine的取消。

    另外一个作用是,传递值,可以将一些特殊值塞入context中,在后续可以取出来。

    ok,我们简单总结下context的作用:

    1. 控制goroutine的取消
    2. 值传递

    下面我们从代码层面说明,先看最简单的值传递。

    1.值传递

    1.1 context.WithValue值传递

    这个最简单,需要注意的是

    1. 这里的key不能用golang的内置类型,需要自定义一个类型;
    2. Value() 取出之前的存的值
    package main
    
    import (
        "context"
        "fmt"
    )
    
    // 上下文值类型 需要自定义类型
    type mystring string
    
    func main() {
        // mystring("abc") 将“bac"转换成mystring类型
        ctx := context.WithValue(context.Background(), mystring("abc"), "hello")
        doTask(ctx)
    }
    
    func doTask(ctx context.Context) {
        // 从上下文中取出值
        fmt.Println(ctx.Value(mystring("abc")))
    }
    
    // hello
    

    2. 取消

    2.1 取消概述

    下面我们来看下取消,取消主要有两种:

    1. 主动调用cancel
    2. 到期取消timeout / deadline

    如何实现goroutine的取消呢?
    是通过Done()方法实现,它是通道;在超时或者手动取消后,会有值弹出;
    在后续goroutine中通过监测Done()通道实现取消。

    PS:

    1. 在context建立后,需要cancel保证资源的释放
    2. 在取消后,多次调用Done()都会有值返回

    好啦!下面看代码

    2.2 手动cancel

    package main
    
    import (
        "context"
        "log"
        "time"
    )
    
    func main() {
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel() // 保证资源释放
    
        go doTask(ctx)              // goroutine中做任务
        time.Sleep(5 * time.Second) // 5s 后触发取消
        cancel()
        time.Sleep(time.Second) // 等待1s后退出 看到 ctx.Done内容
    }
    
    func doTask(ctx context.Context) {
        for {
            select {
            case <-ctx.Done(): // 收到取消
                log.Printf("取消啦:%s\n", ctx.Err())
                return // 退出for循环
            default:
            }
    
            time.Sleep(time.Second) //耗时间操作
            log.Printf("do task ...\n")
        }
    }
    
    // 2023/11/18 16:42:04 do task ...
    // 2023/11/18 16:42:05 do task ...
    // 2023/11/18 16:42:06 do task ...
    // 2023/11/18 16:42:07 do task ...
    // 2023/11/18 16:42:08 do task ...
    // 2023/11/18 16:42:08 取消啦:context canceled
    

    2.3 超时取消

    通过context.WithTimeout或者context.Deadline实现
    这两者使用非常相似,差别在于一个传递的是到期时间,另外一个传时间间隔。

    // 传时间间隔
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    // 传时间
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
    

    完整代码如下:

    package main
    
    import (
        "context"
        "log"
        "sync"
        "time"
    )
    
    var w sync.WaitGroup
    
    func main() {
        // 5s超时间
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
    
        w.Add(1)
        go doTask(ctx) // goroutine中做任务
    
        w.Wait() // 等待执行完
    }
    
    func doTask(ctx context.Context) {
        for {
            select {
            case <-ctx.Done(): // 操时收到
                log.Printf("取消啦:%s\n", ctx.Err())
                w.Done()
                return // 退出for循环
            default:
            }
    
            time.Sleep(time.Second) //耗时间操作
            log.Printf("do task ...\n")
        }
    }
    
    // 2023/11/18 16:51:40 do task ...
    // 2023/11/18 16:51:41 do task ...
    // 2023/11/18 16:51:42 do task ...
    // 2023/11/18 16:51:43 do task ...
    // 2023/11/18 16:51:44 do task ...
    // 2023/11/18 16:51:44 取消啦:context deadline exceeded
    

    2.4 多层取消

    上面我们演示的都是一层的gorotine的取消,下面看看多层取消(goroutine 又生成goroutine的情况)

    package main
    
    import (
        "context"
        "log"
        "time"
    )
    
    func main() {
        // 5s超时间
        ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5))
        defer cancel()
    
        go doTask(ctx) // goroutine中做任务
    
        time.Sleep(6 * time.Second) // 留一定时间看task输出
    }
    
    func doTask(ctx context.Context) {
        // 子goroutine
        go doChildTask(ctx)
    
        for {
            select {
            case <-ctx.Done(): // 操时收到
                log.Printf("do task取消啦: %s\n", ctx.Err())
                return // 退出for循环
            default:
            }
    
            time.Sleep(time.Second) //耗时间操作
            log.Printf("do task ...\n")
        }
    }
    
    // task的子task
    func doChildTask(ctx context.Context) {
        for {
            select {
            case <-ctx.Done(): // 操时收到
                log.Printf("child task取消啦: %s\n", ctx.Err())
                return // 退出for循环
            default:
            }
    
            time.Sleep(time.Second) //耗时间操作
            log.Printf("do child task ...\n")
        }
    }
    
    
    // 2023/11/18 16:59:34 do child task ...
    // 2023/11/18 16:59:34 do task ...
    // 2023/11/18 16:59:35 do child task ...
    // 2023/11/18 16:59:35 do task ...
    // 2023/11/18 16:59:36 do task ...
    // 2023/11/18 16:59:36 do child task ...
    // 2023/11/18 16:59:37 do child task ...
    // 2023/11/18 16:59:37 do task ...
    // 2023/11/18 16:59:38 do child task ...
    // 2023/11/18 16:59:38 do task ...
    // 2023/11/18 16:59:38 child task取消啦: context deadline exceeded
    // 2023/11/18 16:59:38 do task取消啦: context deadline exceeded
    

    相关文章

      网友评论

          本文标题:golang context 看这篇就够啦

          本文链接:https://www.haomeiwen.com/subject/upfuwdtx.html