Анализ исходного кода контекстного анализа go

Go

открытие

Перед исходным кодом нет секретов. Во второй части серии анализов контекста в этой статье будет проанализировано, как контекст выполняет обещанные функции и внутренние характеристики с точки зрения исходного кода. В этой статье в основном рассматриваются следующие четыре точки зрения: интерфейс в контексте, типы контекста, реализация доставки контекста и реализация триггера отмены на уровне контекста.

интерфейс в контексте

ПредыдущийКак использовать анализ контекста goВы можете видеть, что сам пакет контекста содержит несколько функций экспорта, в том числе WithValue, WithTimeout и т. д. Независимо от того, является ли это начальной конструкцией контекста или проведением контекста, основным типом интерфейса является context.Context, и любой контекст также реализует этот интерфейс. , включая контекст значения.

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

Сколько контекстов существует?

Поскольку все контексты должны реализовывать контекст, сколько видов контекстов существует, включая структуры, которые не видны напрямую (не экспортируются)?4 вида.

  • Тип 1: emptyCtx, источник контекста

emptyCtx определяется следующим образом

type emptyCtx int

Чтобы уменьшить давление gc, emptyCtx на самом деле является int и реализует интерфейс Context таким образом, чтобы ничего не делать.Помните, что в пакете context есть две функции для инициализации контекста.

func Background() Context
func TODO() Context

Тип реализации, возвращаемый этими двумя функциями, — emptyCtx, а в пакете контекста реализованы две глобальные переменные типа emptyCtx: background, todo, которые определяются следующим образом.

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

Вышеуказанные две функции по очереди соответствуют этим двум глобальным переменным. На данный момент мы можем определенно сказать, что корневой узел контекста — это глобальная переменная int, а Background() и TODO() — одно и то же.Поэтому никогда не используйте nil в качестве контекста и, с простой для понимания точки зрения, используйте TODO, не задумываясь о том, следует ли передавать и как передавать контекст, и используйте Background() в других случаях, например, при запросе записи для инициализации. контекст

  • Тип 2: cancelCtx, душа механизма отмены

Механизм отмены cancelCtx — это внутренняя реализация ручной отмены и отмены по тайм-ауту, которая определяется следующим образом.

type cancelCtx struct {
	Context

	mu       sync.Mutex
	done     chan struct{}
	children map[canceler]struct{}
	err      error 
}

Здесь mu — это ключ к безопасности параллелизма контекста, done — это ключ к уведомлению, а дочерняя структура хранения — это наиболее часто используемый способ внутреннего управления контекстом.

  • Тип 3: timerCtx, дополнение сцены к механизму отмены

timerCtx содержит cancelCtx, а далее через таймер реализуется функция отмены по времени, которая определяется следующим образом

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

Здесь крайний срок выполняет только краевые функции, такие как запись и String(), а таймер является ключевым.

  • Тип 4: valueCtx, передать по значению

valueCtx — последний из четырех типов. Он используется только для передачи значений. Конечно, его также можно передавать. Можно передавать все контексты. Определение выглядит следующим образом.

type valueCtx struct {
	Context
	key, val interface{}
}

Поскольку некоторые люди думают, что контекст должен использоваться только для передачи значений, некоторые люди считают, что механизм отмены контекста является ядром, поэтому ниже также делается отдельное введение в valueCtx.Вы можете следовать своим собственным бизнес-сценариям, поняв внутреннюю реализацию Сделайте компромисс (вы можете использовать глобальную структуру, карту и т. д. для передачи значений).

Является ли нижний слой контекста значения картой?

В приведенном выше определении valueCtx мы видим, что нижний слой контекста значения не является картой, но каждое отдельное отображение kv соответствует valueCtx, Когда передается несколько значений, создается несколько ctx. В то же время это причина, по которой контекст значения не может передавать значения от младшего к высокому.

Ключ и значение valueCtx являются типами интерфейса.При вызове WithValue внутренняя функция сначала определит, имеет ли ключ сопоставимый тип (тот же, что и ключ в карте) посредством отражения, а затем назначит ключ

При вызове Value он сначала будет искать соответствующий ключ в этом контексте, если не найдёт, будет искать рекурсивно в родительском контексте, поэтому Value может передавать значения сверху вниз.

Как передается контекст

Прежде всего ясно, что любой контекст транзитивен, и внутренний механизм транзитивности можно понимать как:Как обрабатывать контекст родитель-потомок при вызове WithCancel, WithTimeout, WithValue. С точки зрения транзитивности, несколько функций With* реализуются внутри функции propagateCancel. Ниже в качестве примера используется функция WithCancel.

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

newCancelCtx — это процесс, в котором cancelCtx назначает родительский контекст, а propagateCancel устанавливает связь между родительским и дочерним контекстами.

propagateCance определяется следующим образом

func propagateCancel(parent Context, child canceler) {
	if parent.Done() == nil {
		return // parent is never canceled
	}
	if p, ok := parentCancelCtx(parent); ok {// context包内部可以直接识别、处理的类型
		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 {// context包内部不能直接处理的类型,比如type A struct{context.Context},这种静默包含的方式
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

1. Если parent.Done равен нулю, ничего не будет сделано, потому что родительский контекст никогда не будет отменен, например TODO(), Background(), WithValue и т. д. 2. parentCancelCtx возвращает логический тип ok в соответствии с типом родительского контекста.Когда ok равно true, необходимо установить дочерние элементы, соответствующие родителю, и сохранить отношение сопоставления родитель->дочерний (два типа cancelCtx и timerCtx будут установиться, и тип valueCtx всегда будет искать вверх. , и цикл идет вверх, потому что нужна отмена, а потом найти наиболее разумный.), где ключом детей является интерфейс отмены, который не может обрабатывать все внешние типы, так что будет еще, см. приведенные выше комментарии к коду для примеров. Для других внешних типов прямой переходной связи не устанавливается. parentCancelCtx определяется следующим образом

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

Как контекст вызывает отмену

При описании реализации транзитивности выше он также включает в себя часть кода механизма отмены.Исходный код здесь не приводится, но он будет объяснен на основе приведенного выше исходного кода. Для нескольких контекстов процесс переноса аналогичен, но механизм отмены отличается, для каждого типа я объясню их по порядку. Различные типы контекстов могут быть отменены в одной ссылке, но отмена каждого контекста может быть вызвана только одним условием, поэтому механизм отмены каждого контекста будет представлен ниже отдельно (комбинированные сценарии отмены, в порядке поступления). обслуживаемый базовый принцип, независимо от того, какое условие срабатывает, он передаст вызов на отмену). Здесь важны две конструкции:

  1. Функция отмены является идемпотентной и может вызываться несколько раз.
  2. Контекст содержит канал готовности, который можно использовать для подтверждения отмены и уведомления об отмене.
  • тип cancelCtx

cancelCtx возьмет на себя инициативу отмены, в процессе отмены сверху вниз он пройдет через дочерний контекст, а затем, в свою очередь, возьмет на себя инициативу отмены. Функция отмены определяется следующим образом

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
	if err == nil {
		panic("context: internal error: missing cancel error")
	}
	c.mu.Lock()
	if c.err != nil {
		c.mu.Unlock()
		return // already canceled
	}
	c.err = err
	if c.done == nil {
		c.done = closedchan
	} else {
		close(c.done)
	}
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}
  • тип timerCtx

WithTimeout реализуется WithDeadline, все они соответствуют типу timerCtx. Из определения функции parentCancelCtx мы знаем, что timerCtx также записывает отношения контекста родитель-потомок. Однако timerCtx инициирует вызов отмены через таймер таймера, и частичная реализация выглядит следующим образом.

	if c.err == nil {
	    c.timer = time.AfterFunc(dur, func() {
	        c.cancel(true, DeadlineExceeded)
            })
	}
  • молча содержать контекст

В настоящее время я думаю только о случае молчаливого включения, то есть типа A struct{context.Context}. Через parentCancelCtx и propagateCancel мы знаем, что этот контекст не будет устанавливать прямое соединение между родительским и дочерним контекстами, но обнаружит канал done через отдельную горутину, чтобы определить, нужно ли запускать функцию отмены для ссылки. часть propagateCancel для реализации.

конец

Реализация Context не сложна, но действительно незаметна в реальной разработке. Один штрих может быть искусным в сцене сцены, я могу научиться на исходном уровне, чтобы понять реализацию контекста, в некоторых экстремальных сценах, таких как молчание, содержащее контекст, также можно быть осторожным и знать, что это так, спасибо .