8

golang context包 导读

 3 years ago
source link: https://blog.csdn.net/oqqYuan1234567890/article/details/106416860
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang context包 导读

皿小草 2020-05-29 00:59:27 132
分类专栏: golang

以cancelContext 为例
先看使用

package main

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

func main() {
	back := context.Background() // 占位

	child1, cancel1 := context.WithCancel(back)

	child1_1, _ := context.WithCancel(child1)

	child1_2, cancel1_2 := context.WithCancel(child1)

	go func() {
		for range back.Done() {
			fmt.Println("background done")
			break
		}
	}()

	go func() {
		for {
			select {
			case <-back.Done():
				fmt.Println(time.Now().Format(time.RFC3339), "back done")
				return
			}
		}
	}()

	go func() {
		for {
			select {
			case <-child1.Done():
				fmt.Println(time.Now().Format(time.RFC3339), "child1 done")
				return
			}
		}
	}()

	go func() {
		for {
			select {
			case <-child1_1.Done():
				fmt.Println(time.Now().Format(time.RFC3339), "child1_1 done")
				return
			}
		}
	}()

	go func() {
		for {
			select {
			case <-child1_2.Done():
				fmt.Println(time.Now().Format(time.RFC3339), "child1_2 done")
				return
			}
		}
	}()

	go func() {
		time.Sleep(1 * time.Second)
		// 关 cancel1
		cancel1_2()
		time.Sleep(3 * time.Second)
		cancel1()
	}()

	time.Sleep(10 * time.Second)
}

上述代码的功能比较简单,child1是作为parent, child1_1 child1_2是‘children’。程序先sleep 1秒, 调用child1_2关联的cancel方法,再sleep 3s, 调用 调用child1关联的cancel方法

2020-05-29T00:20:31+08:00 child1_2 done
2020-05-29T00:20:34+08:00 child1_1 done
2020-05-29T00:20:34+08:00 child1 done

可以看到,child1_2 先收到了Done信号,再隔3s, child1_1 child1 也分别收到了Done信号
也就是说,父亲可以触发自身与孩子的Done信号,但孩子只会触发自己的Done信号(没有下一级孩子的前提)

go/src/context/context.go

顶层interface

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

Context的设计,主要是解决上下文的数据以及通信问题。尤其是复杂拓扑数据结构的场景,一个‘全局’上下文可以大大简化API的设计。在http包中有大量的应用。

var (
	background = new(emptyCtx)
)
func Background() Context {
	return background
}

Background返回的是一个空的context,为什么需要一个设计一个emptyContext?
主要是因为context的风格是链式调用,emptyContext可以作为初始化的传参使用。

WithCancel 相关

type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     chan struct{}         // created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
}

可以看到,cancelCtx 有关联的children cancelCtx ,也就是一对多的关系。实际场景可以是很复杂的树结构,这类场景适用context的好处就体现出来了,可以针对自身与子节点做Done信号触发。

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

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
	if parent.Done() == nil {
		return // parent is never canceled
	}
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

// parentCancelCtx follows a chain of parent references until it finds a
// *cancelCtx. This function understands how each of the concrete types in this
// package represents its parent.
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	for {
		switch c := parent.(type) {
		case *cancelCtx:
			return c, true
		case *timerCtx:
			return &c.cancelCtx, true
		case *valueCtx:
			parent = c.Context
		default:
			return nil, false
		}
	}
}

parentCancelCtx 对于context包的cancelCtx timerCtx, 不需要新开goroutine, 对于其它类型的ctx,则需要开一个goroutine来接受Done信号,然后下发children

WithDeadline 相关

timerCtx 
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

这个比cancelCtx主要了一个timer

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) }
}

AfterFunc 这里会新起一个goroutine,等到超时就会自动调用cancel方法


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK