Go context

1. 简介

Go语言的context包是用于在并发编程中传递请求作用域的工具。它解决了在多个goroutine之间传递请求相关数据、控制请求超时、取消请求等问题。

2. 核心接口

Go语言中context包的核心接口是context.Context。它定义了用于传递请求作用域的方法和属性。Context接口包含以下几个主要方法:

  1. Deadline() (deadline time.Time, ok bool):返回上下文的截止时间(deadline)。如果上下文没有设置截止时间,ok会返回false。

  2. Done() <-chan struct{}:返回一个通道,当上下文被取消或超时时,该通道会被关闭。

  3. Err() error:返回上下文被取消的原因。如果上下文没有被取消,则返回nil。

  4. Value(key interface{}) interface{}:获取与指定键相关联的值。这对于在请求范围内传递数据很有用。

除了这些方法之外,Context接口还包含了一些私有方法,这些方法主要是用于包内实现,而不是供外部使用。

通过这些方法,我们可以控制请求的超时、取消请求,以及在并发编程中传递请求相关的值,使得程序的并发处理更加可靠和灵活。

3. 核心方法

3.1. context.WithCancel

context.WithCancel函数用于创建一个可取消的context,可以通过调用返回的cancel函数来取消该context。下面是一个Go语言代码演示如何使用context.WithCancel

package main

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

func longRunningTask(ctx context.Context) {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Println("任务正在执行...")
		case <-ctx.Done():
			fmt.Println("任务被取消")
			return
		}
	}
}

func main() {
	// 创建一个父context
	parentCtx := context.Background()

	// 创建一个可取消的子context,并得到一个用于取消的函数
	ctx, cancel := context.WithCancel(parentCtx)

	// 启动长时间运行的任务,传入子context
	go longRunningTask(ctx)

	// 等待3秒后取消任务
	time.Sleep(3 * time.Second)
	cancel()

	// 等待一段时间,以确保长时间运行的任务完成
	time.Sleep(2 * time.Second)
}

在上面的代码中,我们首先创建了一个父context,使用context.Background()函数。然后,我们使用context.WithCancel函数创建了一个可取消的子context,并得到了一个cancel函数。该函数可以用于取消子context。在longRunningTask函数中,我们使用select语句来等待任务执行或者收到子context的取消信号。

主goroutine等待3秒后,我们调用cancel函数来取消子context,从而触发长时间运行的任务接收到取消信号而退出。然后,我们等待一段时间,以确保长时间运行的任务完成。

运行这个代码,您会看到类似以下内容的输出:

任务正在执行...
任务正在执行...
任务正在执行...
任务被取消

3.2. context.WithTimeout

context.WithTimeout函数用于创建一个带有超时的context,超过指定的时间后,context会自动被取消。下面是一个Go语言代码演示如何使用context.WithTimeout

package main

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

func longRunningTask(ctx context.Context) {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Println("任务正在执行...")
		case <-ctx.Done():
			fmt.Println("任务被取消或超时")
			return
		}
	}
}

func main() {
	// 创建一个带有5秒超时的context
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 启动长时间运行的任务,传入带超时的context
	go longRunningTask(ctx)

	// 等待一段时间,以确保长时间运行的任务完成
	time.Sleep(8 * time.Second)
}

在上面的代码中,我们使用context.WithTimeout函数创建了一个带有5秒超时的context,并得到了一个cancel函数。然后,我们启动了一个长时间运行的任务,传入了这个带超时的context。

主goroutine等待8秒后,超过了context的超时时间。在超过5秒后,带超时的context会自动取消,长时间运行的任务接收到了取消信号而退出。

运行这个代码,您会看到类似以下内容的输出:

任务正在执行...
任务正在执行...
任务正在执行...
任务正在执行...
任务正在执行...
任务被取消或超时

这证明了在5秒后,带超时的context成功地取消了长时间运行的任务,因为任务超过了指定的超时时间。

3.3. context.WithDeadline

context.WithDeadline函数用于创建一个带有截止时间的context,超过指定的时间后,context会自动被取消。下面是一个Go语言代码演示如何使用context.WithDeadline

package main

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

func longRunningTask(ctx context.Context) {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Println("任务正在执行...")
		case <-ctx.Done():
			fmt.Println("任务被取消或超时")
			return
		}
	}
}

func main() {
	// 设置截止时间为5秒后
	deadline := time.Now().Add(5 * time.Second)

	// 创建一个带有截止时间的context
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel()

	// 启动长时间运行的任务,传入带截止时间的context
	go longRunningTask(ctx)

	// 等待一段时间,以确保长时间运行的任务完成
	time.Sleep(8 * time.Second)
}

在上面的代码中,我们使用time.Now().Add(5 * time.Second)来设置截止时间为当前时间加上5秒。然后,我们使用context.WithDeadline函数创建了一个带有截止时间的context,并得到了一个cancel函数。接着,我们启动了一个长时间运行的任务,传入了这个带截止时间的context。

主goroutine等待8秒后,超过了context的截止时间。在超过5秒后,带截止时间的context会自动取消,长时间运行的任务接收到了取消信号而退出。

运行这个代码,您会看到类似以下内容的输出:

任务正在执行...
任务正在执行...
任务正在执行...
任务正在执行...
任务正在执行...
任务被取消或超时

这证明了在截止时间到达后,带截止时间的context成功地取消了长时间运行的任务。

3.4. WithValue

context.WithValue函数用于创建一个带有键值对的context,这些键值对可以在整个context范围内传递。下面是一个Go语言代码演示如何使用context.WithValue

package main

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

type keyType string

func longRunningTask(ctx context.Context) {
	if value, ok := ctx.Value(keyType("name")).(string); ok {
		fmt.Printf("任务正在执行,欢迎 %s\n", value)
	} else {
		fmt.Println("任务正在执行...")
	}
}

func main() {
	// 创建一个带有键值对的context
	ctx := context.WithValue(context.Background(), keyType("name"), "John")

	// 启动长时间运行的任务,传入带键值对的context
	go longRunningTask(ctx)

	// 等待一段时间,以确保长时间运行的任务完成
	time.Sleep(time.Second)
}

在上面的代码中,我们使用context.WithValue函数创建了一个带有键值对的context,并将"John"作为值与"keyType"作为键关联起来。接着,我们启动了一个长时间运行的任务,并在任务中根据键来获取传递的值,然后根据值进行相应的输出。

在主goroutine中,我们使用time.Sleep(time.Second)来保持程序运行,以便长时间运行的任务有足够的时间来执行。

运行这个代码,您会看到类似以下内容的输出:

任务正在执行,欢迎 John

这证明了我们成功地在长时间运行的任务中传递了键值对,并根据传递的值做了相应的处理。这种方式可以在整个context范围内传递请求相关的值,非常适合需要在并发处理中共享信息的场景。

3.5. 组合使用

在Go语言中,您可以组合使用context.WithCancelcontext.WithTimeoutcontext.WithDeadlinecontext.WithValue来实现更复杂的场景。下面是一个示例代码演示了这种组合用法:

package main

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

type keyType string

func longRunningTask(ctx context.Context) {
	for {
		select {
		case <-time.After(1 * time.Second):
			if value, ok := ctx.Value(keyType("name")).(string); ok {
				fmt.Printf("任务正在执行,欢迎 %s\n", value)
			} else {
				fmt.Println("任务正在执行...")
			}
		case <-ctx.Done():
			fmt.Println("任务被取消或超时")
			return
		}
	}
}

func main() {
	// 创建一个带有截止时间的context,截止时间为当前时间加上5秒
	deadline := time.Now().Add(5 * time.Second)
	ctx, cancel := context.WithDeadline(context.Background(), deadline)
	defer cancel()

	// 在带截止时间的context中添加一个键值对
	ctxWithValue := context.WithValue(ctx, keyType("name"), "John")

	// 启动长时间运行的任务,传入带截止时间和键值对的context
	go longRunningTask(ctxWithValue)

	// 等待一段时间,以确保长时间运行的任务完成
	time.Sleep(8 * time.Second)
}

在上面的代码中,我们首先使用context.WithDeadline函数创建了一个带有截止时间的context,并得到了一个cancel函数。然后,我们使用context.WithValue函数在该context中添加了一个键值对。

接着,我们启动了一个长时间运行的任务,并传入了带有截止时间和键值对的context。在任务中,我们在每秒钟输出欢迎信息,如果在context中存在"name"键值对,那么输出欢迎该名字的信息。

主goroutine等待8秒后,超过了context的截止时间。在超过5秒后,带截止时间的context会自动取消,长时间运行的任务接收到了取消信号而退出。

运行这个代码,您会看到类似以下内容的输出:

任务正在执行,欢迎 John
任务正在执行,欢迎 John
任务正在执行,欢迎 John
任务正在执行,欢迎 John
任务正在执行,欢迎 John
任务被取消或超时

这证明了在截止时间到达后,带截止时间的context成功地取消了长时间运行的任务,并且任务在上下文中获取到了传递的键值对。这种组合使用的方式非常灵活,可以根据需求进行不同的扩展和组合。

4. 核心思想

在Go语言中,context的上下层关系可以通过context.WithXXX函数来构建。每个WithXXX函数都会返回一个新的context,该context会继承前一个context的属性,并在此基础上添加新的功能。下面是一个演示context上下层关系的示例代码:

package main

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

func longRunningTask(ctx context.Context, name string) {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Printf("%s 正在执行任务...\n", name)
		case <-ctx.Done():
			fmt.Printf("%s 任务被取消或超时\n", name)
			return
		}
	}
}

func main() {
	// 创建一个父context,设置超时时间为5秒
	parentCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 在父context的基础上,创建一个子context,继承父context的超时时间
	childCtx, _ := context.WithCancel(parentCtx)

	// 启动两个长时间运行的任务,分别使用子context和父context
	go longRunningTask(childCtx, "任务1")
	go longRunningTask(parentCtx, "任务2")

	// 主goroutine等待3秒后取消子context,模拟任务1超时
	time.Sleep(3 * time.Second)
	cancel()

	// 等待一段时间,以确保长时间运行的任务2完成
	time.Sleep(2 * time.Second)
}

在上面的代码中,我们首先使用context.WithTimeout函数创建了一个父context,并设置了超时时间为5秒。然后,我们使用context.WithCancel函数在父context的基础上创建了一个子context,并继承了父context的超时时间。

接着,我们启动了两个长时间运行的任务,分别使用子context和父context。其中,任务1使用子context,任务2使用父context。在任务1中,我们在每秒钟输出任务正在执行的信息。在任务2中,我们同样在每秒钟输出信息。

主goroutine等待3秒后,我们调用了cancel函数,取消了子context,模拟了任务1的超时情况。而任务2仍然会继续执行。

运行这个代码,您会看到类似以下内容的输出:

任务1 正在执行任务...
任务2 正在执行任务...
任务1 任务被取消或超时
任务2 正在执行任务...
任务2 正在执行任务...
任务2 正在执行任务...
任务2 任务被取消或超时

这证明了在父context的基础上创建了子context,并且子context继承了父context的超时时间。在子context中调用cancel函数会取消该context及其衍生的所有子context,从而导致长时间运行的任务接收到取消信号而退出。而父context不受子context的影响,继续执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yimtcode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值