【Go系列】 Context的使用

承上启下

        我们在上篇文章中介绍了Sync,可以通过多种同步原语来处理资源竞争的问题。同样的,今天要介绍的context可以用于并发控制的,除此以外,还有很多其他的方法,我们一起来看看。

开始学习

在Go语言中,context包提供了一种机制,允许传递请求相关的数据、取消信号、截止时间以及其他请求范围的值跨API边界和进程边界。它是处理并发、超时和取消操作的重要工具。

context的基本概念

context包的核心是Context接口,它定义了以下方法:

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

以下是这些方法的简要说明:

  • Deadline():返回一个代表此上下文应该被取消的时间,即它的截止时间。如果没有设置截止时间,ok将会是false
  • Done():返回一个通道(channel),这个通道在上下文被取消或超时时关闭。当通道关闭时,意味着相关的操作应该停止并且清理资源。
  • Err():当Done()通道关闭后,返回一个错误,解释上下文被取消的原因。如果上下文没有被取消,返回nil
  • Value():返回此上下文中与key相关联的值,如果没有值与key相关联,则返回nil

创建和衍生context

context包提供了一些函数来创建和衍生上下文:

  • Background():返回一个空的上下文,它是所有上下文的根,通常用于主函数、初始化和测试,并且不应该被取消。
  • TODO():返回一个空的上下文,当不清楚应该使用哪个上下文或者它还未来得及被设置时,作为占位符使用。
  • WithCancel():返回一个新的上下文和取消函数。调用取消函数会取消该上下文及其所有子上下文。
  • WithDeadline():返回一个新的上下文,它在指定的截止时间被取消。
  • WithTimeout():返回一个新的上下文,它在经过指定的持续时间后被取消。
  • WithValue():返回一个新的上下文,其中包含了键值对。

context的使用原则

  • 不要将context存储在结构体中,而是作为第一个参数传递给需要它的函数。
  • 即使函数不直接使用context,也应该将其传递给其他需要它的函数。
  • 不要传递nil的context,如果没有可用的context,应该使用context.Background()
  • 仅在API边界和进程间传递context,而不是在包之间传递。
  • context的值应该是请求范围的数据,而不是进程范围的数据。

通过遵循这些原则和正确使用context包,可以有效地管理Go程序中的并发控制和请求生命周期。

并发控制

在Go语言中,context包是并发控制的关键工具之一。以下是context如何实现并发控制的几个关键点:

1. 上下文取消

context包允许创建可以取消的上下文,这样就可以通知所有依赖于该上下文的goroutine停止它们的工作。

创建可取消的上下文:

ctx, cancel := context.WithCancel(context.Background())

这里,WithCancel函数返回一个新的上下文ctx和一个cancel函数。调用cancel函数会关闭ctx.Done()通道,这是一个信号,告诉所有监听该通道的goroutine应该停止它们的工作。

在goroutine中使用上下文:

go func(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            // 上下文被取消,执行清理工作并退出goroutine
            return
        default:
            // 执行工作...
        }
    }
}(ctx)

在这个goroutine中,通过监听ctx.Done()通道来检查上下文是否已被取消。如果收到取消信号,goroutine将执行必要的清理工作并退出。

2. 上下文超时

context包还允许设置超时,在超时后自动取消上下文。

创建带超时的上下文:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

这里,WithTimeout函数创建一个新的上下文ctx,它在10秒后自动取消。同样,cancel函数可以用来提前取消上下文。

3. 上下文值传递

context可以携带键值对,允许在goroutine之间传递数据。

在上下文中设置值:

ctx := context.WithValue(context.Background(), "key", "value")

在goroutine中获取值:

value := ctx.Value("key")

实现并发控制的步骤:

  1. 创建上下文:根据需要创建一个基础上下文,并使用WithCancelWithTimeoutWithValue来设置特定的属性。

  2. 传递上下文:将上下文作为参数传递给启动的goroutine或函数。

  3. 监听取消信号:在goroutine中,使用select语句监听ctx.Done()通道,以检测上下文是否被取消。

  4. 响应取消:当ctx.Done()通道关闭时,执行必要的清理工作,并退出goroutine。

  5. 主动取消:在适当的时机(例如错误发生或操作完成)调用cancel函数来取消上下文。

以下是一个简单的例子,展示了如何使用context来控制并发:

package main

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

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d received cancel signal\n", id)
            return
        default:
            fmt.Printf("Worker %d is working...\n", id)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    for i := 0; i < 3; i++ {
        go worker(ctx, i)
    }

    // 等待足够长的时间来观察goroutine的行为
    time.Sleep(10 * time.Second)
}

在这个例子中,三个工作goroutine启动并执行任务。它们都会监听上下文的取消信号。主函数设置了5秒的超时,之后取消上下文,这会导致所有工作goroutine在打印一条消息后退出。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值