Maison  >  Article  >  développement back-end  >  Explication détaillée du contexte Golang

Explication détaillée du contexte Golang

藏色散人
藏色散人avant
2020-09-10 09:30:294003parcourir

Ce qui suit est une explication détaillée du contexte de Golang à partir de la colonne tutoriel golang J'espère que cela sera utile aux amis dans le besoin !

Explication détaillée du contexte Golang

Avant-propos

Oui, aujourd'hui je Au départ, je voulais sortir et jouer. J'ai acheté un billet de train et j'ai encore dormi trop longtemps. . Il n'y a aucun moyen, c'est peut-être la volonté de Dieu, je dois donc résumer le contexte du golang, en espérant faire une rupture avec le contexte.

Dans l'entreprise, lorsque nous écrivons divers services, nous devons utiliser le Contexte comme premier paramètre. Au début, je pensais qu'il était principalement utilisé pour le dépannage et le suivi des liens complets. Mais avec plus de contacts, il s’avère que c’est bien plus que cela.

Texte

1. Explication détaillée du contexte

1.1 Fond de génération

Avant la version 1.7, le contexte n'était toujours pas programmé (y compris golang.org/ x /net/context), l'équipe golang a trouvé que le contexte est assez facile à utiliser et est utilisé dans de nombreux endroits, il y a donc été incorporé La version 1.7 est officiellement entrée dans la bibliothèque standard.

Postures d'utilisation courantes du contexte :
1. En programmation Web, une requête correspond à une interaction de données entre plusieurs goroutines
2. Contrôle du délai d'attente
3. 1.2 La structure sous-jacente du contexte

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

Voici la structure de données sous-jacente du Context Analysons-la :

En même temps, le package définit également l'interface qui doit être. implémenté pour fournir la fonction d'annulation. Cela est principalement dû au fait que le « signal d'annulation et le signal de temporisation » mentionnés plus loin doivent être implémentés.
字段 含义
Deadline 返回一个time.Time,表示当前Context应该结束的时间,ok则表示有结束时间
Done 当Context被取消或者超时时候返回的一个close的channel,告诉给context相关的函数要停止当前工作然后返回了。(这个有点像全局广播)
Err context被取消的原因
Value context实现共享数据存储的地方,是协程安全的(还记得之前有说过map是不安全的?所以遇到map的结构,如果不是sync.Map,需要加锁来进行操作)
// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

Ensuite, la bibliothèque fournit 4 implémentations de contexte avec lesquelles tout le monde peut jouer

Hérite du contexte et implémente également l'interface d'annulation
implémentation Structure Fonction
emptyCtx type emptyCtx int td> Contexte complètement vide, les fonctions implémentées renvoient également nil, elles implémentent simplement l'interface Context
cancelCtx type CancelCtx struct {

Contexte

mu sync.Mutex
实现 结构体 作用
emptyCtx type emptyCtx int 完全空的Context,实现的函数也都是返回nil,仅仅只是实现了Context的接口
cancelCtx type cancelCtx struct {
    Context
    mu    sync.Mutex
    done     chan struct{}
    
    children map[canceler]struct{}
    err      error
    }
继承自Context,同时也实现了canceler接口
timerCtx type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.
    deadline time.Time
}
继承自cancelCtx,增加了timeout机制
valueCtx type valueCtx struct {
    Context
    key, val interface{}
}
存储键值对的数据
done chan struct{} children map[canceler]struct{} erreur d'erreur }
timerCtx type timerCtx struct { CancelCtx timer *time.Timer // Sous CancelCtx.mu. date limite time.Time} hérite de cancelCtx, ajout d'un mécanisme de délai d'attente
valueCtx tapez valueCtx struct {Contexte clé, interface val{}} Données qui stockent les paires clé-valeur

1.3 context的创建

为了更方便的创建Context,包里头定义了Background来作为所有Context的根,它是一个emptyCtx的实例。

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx) // 
)

func Background() Context {
    return background
}

你可以认为所有的Context是树的结构,Background是树的根,当任一Context被取消的时候,那么继承它的Context 都将被回收。

2.context实战应用

2.1 WithCancel

实现源码:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

实战场景:
执行一段代码,控制执行到某个度的时候,整个程序结束。

吃汉堡比赛,奥特曼每秒吃0-5个,计算吃到10的用时
实战代码:

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	eatNum := chiHanBao(ctx)
	for n := range eatNum {
		if n >= 10 {
			cancel()
			break
		}
	}

	fmt.Println("正在统计结果。。。")
	time.Sleep(1 * time.Second)
}

func chiHanBao(ctx context.Context) <-chan int {
	c := make(chan int)
	// 个数
	n := 0
	// 时间
	t := 0
	go func() {
		for {
			//time.Sleep(time.Second)
			select {
			case <-ctx.Done():
				fmt.Printf("耗时 %d 秒,吃了 %d 个汉堡 \n", t, n)
				return
			case c <- n:
				incr := rand.Intn(5)
				n += incr
				if n >= 10 {
					n = 10
				}
				t++
				fmt.Printf("我吃了 %d 个汉堡\n", n)
			}
		}
	}()

	return c
}

输出:

我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
正在统计结果。。。
耗时 6 秒,吃了 10 个汉堡

2.2 WithDeadline & WithTimeout

实现源码:

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
	c := &timerCtx{
		cancelCtx: newCancelCtx(parent),
		deadline:  d,
	}
	propagateCancel(parent, c)
	dur := time.Until(d)
	if dur <= 0 {
		c.cancel(true, DeadlineExceeded) // deadline has already passed
		return c, func() { c.cancel(true, Canceled) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

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

实战场景:
执行一段代码,控制执行到某个时间的时候,整个程序结束。

吃汉堡比赛,奥特曼每秒吃0-5个,用时10秒,可以吃多少个
实战代码:

func main() {
    // ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10))
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	chiHanBao(ctx)
	defer cancel()
}

func chiHanBao(ctx context.Context) {
	n := 0
	for {
		select {
		case <-ctx.Done():
			fmt.Println("stop \n")
			return
		default:
			incr := rand.Intn(5)
			n += incr
			fmt.Printf("我吃了 %d 个汉堡\n", n)
		}
		time.Sleep(time.Second)
	}
}

输出:

我吃了 1 个汉堡
我吃了 3 个汉堡
我吃了 5 个汉堡
我吃了 9 个汉堡
我吃了 10 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 13 个汉堡
我吃了 14 个汉堡
我吃了 14 个汉堡
stop

2.3 WithValue

实现源码:

func WithValue(parent Context, key, val interface{}) Context {
	if key == nil {
		panic("nil key")
	}
	if !reflect.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
	return &valueCtx{parent, key, val}
}

实战场景:
携带关键信息,为全链路提供线索,比如接入elk等系统,需要来一个trace_id,那WithValue就非常适合做这个事。
实战代码:

func main() {
	ctx := context.WithValue(context.Background(), "trace_id", "88888888")
	// 携带session到后面的程序中去
	ctx = context.WithValue(ctx, "session", 1)

	process(ctx)
}

func process(ctx context.Context) {
	session, ok := ctx.Value("session").(int)
	fmt.Println(ok)
	if !ok {
		fmt.Println("something wrong")
		return
	}

	if session != 1 {
		fmt.Println("session 未通过")
		return
	}

	traceID := ctx.Value("trace_id").(string)
	fmt.Println("traceID:", traceID, "-session:", session)
}

输出:

traceID: 88888888 -session: 1

3.context建议

不多就一个。

Context要是全链路函数的第一个参数

func myTest(ctx context.Context)  {
    ...
}

(写好了竟然忘记发送了。。。汗)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer