乐趣区

关于go:Go-语言基础之-Context-详解

之前有兄弟留言想学习一下 Context,他来了,虽迟但到。

前言

在 Go 语言中,Context 是一个十分重要的概念,它用于在不同的 goroutine 之间传递申请域的相干数据,并且能够用来管制 goroutine 的生命周期和勾销操作。本文将深入探讨 Go 语言中 Context 个性 和 Context 的高级应用办法。

根本用法

在 Go 语言中,Context 被定义为一个接口类型,它蕴含了三个办法:

# go version 1.18.10
type Context interface {Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
      Value(key any) any
}
  • Deadline() 办法用于获取 Context 的截止工夫,
  • Done() 办法用于返回一个只读的 channel,用于告诉以后 Context 是否曾经被勾销,
  • Err() 办法用于获取 Context 勾销的起因,
  • Value() 办法用于获取 Context 中保留的键值对数据。

咱们日常编写代码时,Context 对象会被被约定作为函数的第一个参数传递,eg:

func users(ctx context.Context, request *Request) {// ... code}

在函数中,能够通过 ctx 参数来获取相干的 Context 数据,举个超时的 eg:

deadline, ok := ctx.Deadline()
if ok && deadline.Before(time.Now()) {
    // 超时
    return
}

Context 管制 goroutine 的生命周期

在 Go 语言中,goroutine 是一种十分常见的并发编程模型,而 Context 能够被用来管制 goroutine 的生命周期,从而避免出现 goroutine 透露或者不必要的期待操作。

eg,看一下下方代码:

func users(ctx context.Context, req *Request) {
    // 启动一个 goroutine 来解决申请
    go func() {// 解决申请...}()}

下面的代码中,咱们启动了一个 goroutine 来解决申请,然而没有任何形式来管制这个 goroutine 的生命周期,如果这个申请被勾销了,那么这个 goroutine 就会始终存在,直到它实现为止。为了防止这种状况的产生,咱们能够应用 Context 来管制 goroutine 的生命周期,eg:

func users(ctx context.Context, req *Request) {
    // 启动一个 goroutine 来解决申请
    go func(ctx context.Context) {// 解决申请...}(ctx)
}

在下面的代码中,咱们将 Context 对象作为参数传递给了 goroutine 函数,这样在申请被勾销时,goroutine 就能够及时退出。

应用 WithValue() 传递数据

除了用于管制 goroutine 的生命周期,Context 还能够被用来在不同的 goroutine 之间传递申请域的相干数据。为了实现这个目标,咱们能够应用 Context 的 WithValue() 办法,eg:

type key int

const (userKey key = iota)

func users(ctx context.Context, req *Request) {
    // 从申请中获取用户信息
    user := req.GetUser
    // 将用户信息保留到 Context 中
    ctx = context.WithValue(ctx, userKey, user)
    
    // 启动一个 goroutine 来解决申请
    go func(ctx context.Context) {
        // 从 Context 中获取用户信息
        user := ctx.Value(userKey).(*User)
    
        // 解决申请...
    }(ctx)

}
    
    

在下面的代码中,咱们定义了一个 key 类型的常量 userKey,而后在 users() 函数中将用户信息保留到了 Context 中,并将 Context 对象传递给了 goroutine 函数。

在 goroutine 函数中,咱们应用 ctx.Value() 办法来获取 Context 中保留的用户信息。

注:

Context 中保留的键值对数据应该是线程平安的,因为它们可能会在多个 goroutine 中同时拜访。

应用 WithCancel() 勾销操作

除了管制 goroutine 的生命周期和传递数据之外,Context 还能够被用来执行勾销操作。为了实现这个目标,咱们能够应用 Context 的 WithCancel() 办法,eg:

func users(ctx context.Context, req *Request) {
    // 创立一个能够勾销的 Context 对象
    ctx, cancel := context.WithCancel(ctx)

    // 启动一个 goroutine 来解决申请
    go func(ctx context.Context) {
        // 期待申请实现或者被勾销
        select {case <-time.After(time.Second):
            // 申请实现
            fmt.Println("Request finish")
        case <-ctx.Done():
            // 申请被勾销
            fmt.Println("Request canceled")
        }
    }(ctx)

    // 期待一段时间后勾销申请
    time.Sleep(time.Millisecond * 800)
    cancel()}

在下面的代码中,咱们应用 WithCancel() 办法创立了一个能够勾销的 Context 对象,并将勾销操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者被勾销,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。

应用 WithDeadline() 设置截止工夫

除了应用 WithCancel() 办法进行勾销操作之外,Context 还能够被用来设置截止工夫,以便在超时的状况下勾销申请。为了实现这个目标,咱们能够应用 Context 的 WithDeadline() 办法,eg:

func users(ctx context.Context, req *Request) {
    // 设置申请的截止工夫为以后工夫加上 1 秒钟
    ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second))

    // 启动一个 goroutine 来解决申请
    go func(ctx context.Context) {
        // 期待申请实现或者超时
        select {case <-time.After(time.Millisecond * 500):
            // 申请实现
            fmt.Println("Request finish")
            case <-ctx.Done():
            // 申请超时或者被勾销
            fmt.Println("Request canceled or timed out")
        }
    }(ctx)

    // 期待一段时间后勾销申请
    time.Sleep(time.Millisecond * 1500)
    cancel()}

在下面的代码中,咱们应用 WithDeadline() 办法设置了一个截止工夫为以后工夫加上 1 秒钟的 Context 对象,并将超时操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者超时,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。

注:

在应用 WithDeadline() 办法设置截止工夫的时候,如果截止工夫曾经过期,则 Context 对象将被立刻勾销。

应用 WithTimeout() 设置超时工夫

除了应用 WithDeadline() 办法进行截止工夫设置之外,Context 还能够被用来设置超时工夫。为了实现这个目标,咱们能够应用 Context 的 WithTimeout() 办法,eg:

func users(ctx context.Context, req *Request) {
    // 设置申请的超时工夫为 1 秒钟
    ctx, cancel := context.WithTimeout(ctx, time.Second)

    // 启动一个 goroutine 来解决申请
    go func(ctx context.Context) {
        // 期待申请实现或者超时
        select {case <-time.After(time.Millisecond * 500):
            // 申请实现
            fmt.Println("Request completed")
        case <-ctx.Done():
            // 申请超时或者被勾销
            fmt.Println("Request canceled or timed out")
        }
    }(ctx)

    // 期待一段时间后勾销申请
    time.Sleep(time.Millisecond * 1500)
    cancel()}

在下面的代码中,咱们应用 WithTimeout() 办法设置了一个超时工夫为 1 秒钟的 Context 对象,并将超时操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者超时,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。

注:

须要留神的是,在应用 WithTimeout() 办法设置超时工夫的时候,如果超时工夫曾经过期,则 Context 对象将被立刻勾销。

Context 的传递

在一个应用程序中,不同的 goroutine 可能须要共享同一个 Context 对象。为了实现这个目标,Context 对象能够通过函数调用或者网络传输等形式进行传递。

例如,咱们能够在一个解决 HTTP 申请的函数中创立一个 Context 对象,并将它作为参数传递给一个数据库查问函数,以便在查问过程中应用这个 Context 对象进行勾销操作。代码 eg:

func users(ctx context.Context, req *Request) {
    // 在解决 HTTP 申请的函数中创立 Context 对象
    ctx, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()

    // 调用数据库查问函数并传递 Context 对象
    result, err := findUserByName(ctx, req)
    if err != nil {// 解决查问谬误...}

    // 解决查问后果...
}

func findUserByName(ctx context.Context, req *Request) (*Result, error) {
    // 在数据库查问函数中应用传递的 Context 对象
    rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE name = ?", req.Name)
    if err != nil {// 解决查问谬误...}
      defer rows.Close()
      // 解决查问后果...
}

在下面的代码中,咱们在解决 HTTP 申请的函数中创立了一个 Context 对象,并将它作为参数传递给了一个数据库查问函数 findUserByName()。在 findUserByName() 函数中,咱们应用传递的 Context 对象来调用 db.QueryContext() 办法进行查问操作。因为传递的 Context 对象可能会在查问过程中被勾销,因而咱们须要在查问实现后查看查问操作的谬误,以便进行相应的解决。

注:

在进行 Context 的传递时,咱们须要保障传递的 Context 对象是原始 Context 对象的子 Context,以便在须要勾销操作时可能同时勾销所有相干的 goroutine。如果传递的 Context 对象不是原始 Context 对象的子 Context,则勾销操作只会影响到以后 goroutine,而无奈勾销其余相干的 goroutine。

总结

在 Go 语言中,Context 是一个十分重要的个性,包含其根本用法和一些高级用法。Context 能够用于治理 goroutine 的生命周期和勾销操作,避免出现资源透露和死锁等问题,同时也能够进步应用程序的性能和可维护性。

在应用 Context 的时候, 须要留神以下几点:

    1. 在创立 goroutine 时,须要将原始 Context 对象作为参数传递给它。
    1. 在 goroutine 中,须要应用传递的 Context 对象来进行勾销操作,以便可能及时开释相干的资源。
    1. Context 的传递时,须要保障传递的 Context 对象是原始 Context 对象的子 Context,以便在须要勾销操作时可能同时勾销所有相干的 goroutine。
    1. 在应用 WithCancel 和 WithTimeout 办法创立 Context 对象时,须要及时调用 cancel 函数,以便可能及时开释资源。
    1. 在一些场景下,能够应用 WithValue 办法将数据存储到 Context 中,以便在不同的 goroutine 之间共享数据。
退出移动版