惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
T
Threatpost
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Apple Machine Learning Research
Apple Machine Learning Research
酷 壳 – CoolShell
酷 壳 – CoolShell
M
MIT News - Artificial intelligence
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
S
Security Affairs
P
Proofpoint News Feed
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
S
Security @ Cisco Blogs
H
Hacker News: Front Page
Security Archives - TechRepublic
Security Archives - TechRepublic
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
月光博客
月光博客
量子位
博客园_首页
The Last Watchdog
The Last Watchdog
D
DataBreaches.Net
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy International News Feed
The Register - Security
The Register - Security
Schneier on Security
Schneier on Security
H
Help Net Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
F
Full Disclosure
C
Cyber Attacks, Cyber Crime and Cyber Security
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
L
Lohrmann on Cybersecurity
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
Cloudbric
Cloudbric
Microsoft Security Blog
Microsoft Security Blog

博客园 - 自然洒脱

MiniMax 权益码 Token Plan 套餐 9 折优惠 go语言通道 go语言GMP模型 go语言mongodb操作 go语言gorm的CRUD go语言gorm go语言mysql驱动 go语言log相关 go语言包管理 go语言时间相关 go语言序列化和反序列化 go语言的"面向对象" go语言结构体排序 go语言异常处理 go语言接口 go语言结构体(二) go语言结构体(一) go语言递归函数及defer go语言函数作用域及匿名函数
go语言context包
自然洒脱 · 2023-08-18 · via 博客园 - 自然洒脱

context包是在go1.7版本中引入到标准库中的

context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的

context被当作第一个参数(官方建议),并且不断透传下去,基本一个项目代码中到处都是context

context的作用就是在不同的goroutine之间同步请求特定的数据、取消信号以及处理请求的截止日期。

https://pkg.go.dev/context@go1.20#Context

Context其实就是一个接口,定义了四个方法:

type Context interface {
        // 当Context自动取消或者到了取消时间被取消后返回
    Deadline() (deadline time.Time, ok bool)
        // 当Context被取消或者到了deadline返回一个被关闭的channel
    Done() <-chan struct{}
        // 当Context被取消或者关闭后,返回context取消的原因
    Err() error
        // 获取设置的key对应的值
    Value(key any) any
}

创建和衍生context

context包主要提供了两种方式创建context:

  • context.Backgroud()
  • context.TODO()

这两个函数其实只是互为别名,没有差别,官方给的定义是:

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。
  • context.TODO 应该只在不确定应该使用哪种上下文时使用;

所以在大多数情况下,我们都使用context.Background作为起始的上下文向下传递。

上面的两种方式是创建根context,不具备任何功能,具体实践还是要依靠context包提供的With系列函数来进行派生:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context

这四个函数都要基于父Context衍生,通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个

 withValue携带数据

在有trace id需求时,可使用此方法,在使用withVaule时要注意四个事项:

  • 不建议使用context值传递关键参数,关键参数应该显示的声明出来,不应该隐式处理,context中最好是携带签名、trace_id这类值。
  • 因为携带value也是keyvalue的形式,为了避免context因多个包同时使用context而带来冲突,key建议采用内置类型。
  • 下面的例子我们获取trace_id是直接从当前ctx获取的,实际我们也可以获取父context中的value,在获取键值对时,我们先从当前context中查找,没有找到会再从父context中查找该键对应的值直到在某个父context中返回 nil 或者查找到对应的值。
  • context传递的数据中keyvalue都是interface类型,这种类型编译期无法确定类型,所以不是很安全,所以在类型断言时别忘了保证程序的健壮性。

package main

import (
    "context"
    "fmt"
    "strings"

    "github.com/google/uuid"
)

// 自定义新类型作为ctx的key类型,因为直接用string类型,会有提示:
// should not use built-in type string as key for value; define your own type to avoid collisions
type Tstring string


func NewID() string {
    return strings.Replace(uuid.NewString(), "-", "", -1)
}

func gen(ctx context.Context) chan int {
    dst := make(chan int)
    n := 1
    fmt.Println(ctx.Value(Tstring("traceId")))
    go func() {
        for {
            select {
            // 调用cancel方法时,会触发
            case <-ctx.Done():
                return
            case dst <- n:
                n++
            }
        }
    }()
    return dst
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    ctx = context.WithValue(ctx, Tstring("traceId"), NewID())
    // 调cancel方法来对goroutine进行控制
    cancel()
    for v := range gen(ctx) {
        fmt.Println(v)
        if v == 5 {
            break
        }
    }
}

withvalue+cancel

withCancel取消控制

我们往往为了完成一个复杂的需求会开多个gouroutine去做一些事情,这就导致我们会在一次请求中开了多个goroutine确无法控制他们,这时我们就可以使用withCancel来衍生一个context传递到不同的goroutine中,当我想让这些goroutine停止运行,就可以调用cancel来进行取消。

在上面代码中,通过Background创建了一个ctx并返回了一个cancel函数,通过调用cancel函数来传递信号给goroutine,从而达到对goroutine的控制。

 超时控制

通常健壮的程序都是要设置超时时间的,避免因为服务端长时间响应消耗资源,所以一些web框架或rpc框架都会采用withTimeout或者withDeadline来做超时控制,当一次请求到达我们设置的超时时间,就会及时取消,不在往下执行。withTimeoutwithDeadline作用是一样的,就是传递的时间参数不同而已,他们都会通过传入的时间来自动取消Context,这里要注意的是他们都会返回一个cancelFunc方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用cancelFunc去停止定时减少不必要的资源浪费。

withTimeoutWithDeadline不同在于WithTimeout将持续时间作为参数输入而不是时间对象,本质withTimout内部也是调用的WithDeadline

package main

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

func gen(ctx context.Context, cancel context.CancelFunc) {
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            fmt.Println("该结束了", ctx.Err())
            return
        default:
            fmt.Println("循环i为", i)
            if i == 2 {
                // 协程主动调用函数来取消
                cancel()
            }
            time.Sleep(time.Second)
        }
    }
}

func main() {
    // 超时时间为3秒
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    // 调cancel方法来对goroutine进行控制
    defer cancel()
    // 也可通过传递cancel函数,在协程内主动调用
    go gen(ctx, cancel)
    for i := 0; i < 20; i++ {
        time.Sleep(time.Second)
        fmt.Println("协程个数", runtime.NumGoroutine())
    }
}

超时控制

通过超时控制,既可以达到超时时间终止接下来的执行,也可以没有达到超时时间终止接下来的执行