golang context 看這篇就夠啦

context

context字面意思是上下文,它有什么用呢?
它主要用于多gorountine、多層級的goroutine(一個goroutine下又有g(shù)oroutine-子goroutine)的情況下,上層對于開啟的gorotine的取消。

另外一個作用是,傳遞值,可以將一些特殊值塞入context中,在后續(xù)可以取出來。

ok,我們簡單總結(jié)下context的作用:

  1. 控制goroutine的取消
  2. 值傳遞

下面我們從代碼層面說明,先看最簡單的值傳遞。

1.值傳遞

1.1 context.WithValue值傳遞

這個最簡單,需要注意的是

  1. 這里的key不能用golang的內(nèi)置類型,需要自定義一個類型;
  2. Value() 取出之前的存的值
package main

import (
    "context"
    "fmt"
)

// 上下文值類型 需要自定義類型
type mystring string

func main() {
    // mystring("abc") 將“bac"轉(zhuǎn)換成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. 主動調(diào)用cancel
  2. 到期取消timeout / deadline

如何實(shí)現(xiàn)goroutine的取消呢?
是通過Done()方法實(shí)現(xiàn),它是通道;在超時或者手動取消后,會有值彈出;
在后續(xù)goroutine中通過監(jiān)測Done()通道實(shí)現(xiàn)取消。

PS:

  1. 在context建立后,需要cancel保證資源的釋放
  2. 在取消后,多次調(diào)用Done()都會有值返回

好啦!下面看代碼

2.2 手動cancel

package main

import (
    "context"
    "log"
    "time"
)

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 保證資源釋放

    go doTask(ctx)              // goroutine中做任務(wù)
    time.Sleep(5 * time.Second) // 5s 后觸發(fā)取消
    cancel()
    time.Sleep(time.Second) // 等待1s后退出 看到 ctx.Done內(nèi)容
}

func doTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // 收到取消
            log.Printf("取消啦:%s\n", ctx.Err())
            return // 退出for循環(huán)
        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實(shí)現(xiàn)
這兩者使用非常相似,差別在于一個傳遞的是到期時間,另外一個傳時間間隔。

// 傳時間間隔
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ù)

    w.Wait() // 等待執(zhí)行完
}

func doTask(ctx context.Context) {
    for {
        select {
        case <-ctx.Done(): // 操時收到
            log.Printf("取消啦:%s\n", ctx.Err())
            w.Done()
            return // 退出for循環(huán)
        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中做任務(wù)

    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循環(huán)
        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循環(huán)
        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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 為什么需要context 在并發(fā)程序中,由于超時、取消操作或者一些異常情況,往往需要進(jìn)行搶占操作或者中斷后續(xù)操作。...
    陳二狗想吃肉閱讀 729評論 0 2
  • 本文對golang context的源碼進(jìn)行解讀,go version is go1.16.4 linux/amd...
    9_SooHyun閱讀 251評論 0 0
  • [TOC] Golang Context分析 Context背景 和 適用場景 golang在1.6.2的時候還沒...
    AllenWu閱讀 11,628評論 0 30
  • overview Package context defines the Context type, which ...
    wncbbnk閱讀 641評論 0 0
  • 上下文 context.ContextGo 語言中用來設(shè)置截止日期、同步信號,傳遞請求相關(guān)值的結(jié)構(gòu)體。上下文與 G...
    舍是境界閱讀 1,113評論 0 1

友情鏈接更多精彩內(nèi)容