golang context使用小结

发布时间 2023-06-25 10:02:44作者: 夕夢

Go标准库中的context包,提供了goroutine之间的传递信息的机制,信号同步,除此之外还有超时(timeout)和取消(cancel)机制。概括起来,Context可以控制子goroutine的运行,超时控制的方法调用,可以取消的方法调用。

context核心数据结构

  1. Context interface
type Context interface {
  Deadline() (deadline time.Time, ok bool) 
  // 返回这个Context被取消的截止时间,如果没有设置截止时间,ok的值返回的是false
  Done() <-chan struct{}                   
  // 返回一个只读的channel 一般用来监听context的取消 当context关闭后,Done()返回一个被关闭的管道,关闭的管道仍然是可读的,据此goroutine可以收到关闭请求,当context还未关闭时,Done()返回nil
  Err() error
  // 当context关闭后,Err()返回context的关闭原因 当context还未关闭时,Err()返回nil
  Value(key any) any                       
  // 返回此cxt中指定key对应的value 用于goroutine间传递信息
}
  1. 生产对应的结构体
    context包提供了4个方法创建不同类型的context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)              // CancelCtx 
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)          // DeadLineCtx
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) // TimeoutCtx
func WithValue(parent Context, key, val any) Context                          // ValueCtx
  1. emptyCtx

使用

1.WithCancel

package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go gOne(ctx, "任务一")
	time.Sleep(time.Second * 10)
	cancel()
	time.Sleep(time.Second * 2)
	fmt.Println("exit")
}

func gOne(ctx context.Context, name string) {
	go gTwo(ctx, "任务二")
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name + ":任务已停止")
			return
		case <-time.After(time.Second * 1):
			fmt.Println(name + "任务执行中")
		}
	}
}

func gTwo(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name + ":任务已停止")
			return
		case <-time.After(time.Second * 2):
			fmt.Println(name + "任务执行中")
		}
	}
}

2.WithDeadline WithTimeout
这两个方法本质是一样的 WithTimeout也是调用的WithDeadline方法

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}
package main

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

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

	go gTimeOne(ctx, "任务一")

	time.Sleep(10 * time.Second)
	fmt.Println("exit")
}

func gTimeOne(ctx context.Context, name string) {
	go gTimeTwo(ctx, "任务二")
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name + ":任务已停止")
			return
		case <-time.After(time.Second * 1):
			fmt.Println(name + "任务执行中")
		}
	}
}

func gTimeTwo(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name + ":任务已停止")
			return
		case <-time.After(time.Second * 2):
			fmt.Println(name + "任务执行中")
		}
	}
}

3.WithValue
使用valueContext在协程之间共享数据

package main

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

func main() {
	ctx := context.WithValue(context.Background(), "param", "1234")

	go gWork(ctx)

	time.Sleep(time.Second * 5)
	fmt.Println("exit")
}

func gWork(ctx context.Context) {
	value := ctx.Value("param").(string)
	fmt.Println(value)
}

由于valueCtx不具有Done() 相关方法的实现,一版是配合另几个方法使用

package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	valueCtx := context.WithValue(ctx, "param", "1234")

	go gWork2(valueCtx)
	time.Sleep(time.Second * 5)
	cancel()

	time.Sleep(time.Second * 10)
	fmt.Println("exit")
}

func gWork2(ctx context.Context) {
	value := ctx.Value("param").(string)
	fmt.Println(value)
	for {
		select {
		case <-ctx.Done():
			fmt.Println("任务已停止")
			return
		case <-time.After(time.Second * 2):
			fmt.Println("任务执行中")
		}
	}
}

gin中context的使用

gin接口超时处理
1.利用context.WithTimeout的特性去实现

// 入侵式
https://gist.github.com/montanaflynn/ef9e7b9cd21b355cfe8332b4f20163c1
// 非入侵式,无须修改原来代码(可能有bug)
https://vearne.cc/archives/39130

2.官方提供的插件

https://github.com/gin-contrib/timeout