Контекст

Что такое контекст?

В Go, контекст (context) используется для передачи сигналов относительно отмены операций, таймаутов и передачи метаданных между API. Это особенно полезно в ситуациях, когда у вас есть множество горутин и вы хотите контролировать их выполнение.

В Go есть пакет context, который предоставляет функции и типы для работы с контекстами. Вы можете создать контекст с помощью функций context.Background() или context.TODO(). Затем вы можете создать дочерний контекст с помощью функций context.WithCancel(), context.WithDeadline(), context.WithTimeout(), или context.WithValue().

Когда контекст отменяется, все горутины, которые получают этот контекст, получают сигнал об отмене, и они должны прекратить свою работу.

Для чего используется контекст?

Контекст в Go используется для нескольких целей:

  • Отмена операций: Контекст может быть использован для отмены операций. Это особенно полезно, когда у вас есть долгосрочная операция, которая может быть отменена в любой момент. Когда контекст отменяется, все горутины, которые получают этот контекст, получают сигнал об отмене.
  • Таймауты: Контекст также может быть использован для установки таймаутов на операции. Это может быть полезно, когда вы хотите ограничить время выполнения операции.
  • Передача метаданных: Контекст может быть использован для передачи метаданных между API. Это может быть полезно, когда у вас есть информация, которую нужно передать между различными частями вашего приложения, например, информация о трассировке.
  • Контроль над горутинами: Контекст позволяет контролировать выполнение горутин, особенно когда у вас есть множество горутин и вы хотите контролировать их выполнение.

Какие есть виды контекстов?

  • Background: Это базовый контекст, который обычно используется, когда другой контекст не доступен. Это обычно используется в main функции, в тестах и в пакетах, которые не знают, в каком контексте они будут использоваться. Создается с помощью функции context.Background().
  • TODO: Этот контекст также используется, когда контекст не доступен. Он обычно используется, когда не ясно, какой контекст использовать, или когда контекст будет доступен в будущем. Создается с помощью функции context.TODO().
  • WithCancel: Этот контекст предоставляет возможность отмены. Когда функция cancel вызывается, все горутины, которые слушают этот контекст, получают сигнал об отмене. Создается с помощью функции context.WithCancel(parentContext).
  • WithDeadline и WithTimeout: Эти контексты предоставляют возможность установить время, после которого контекст будет автоматически отменен. WithDeadline принимает конкретное время, после которого контекст будет отменен, а WithTimeout принимает продолжительность времени, после которой контекст будет отменен. Создаются с помощью функций context.WithDeadline(parentContext, deadline) и context.WithTimeout(parentContext, timeout) соответственно.
  • WithValue: Этот контекст предоставляет возможность связать значения с контекстом, которые затем могут быть извлечены в другом месте в коде. Создается с помощью функции context.WithValue(parentContext, key, value).

Как устроен контекст?

Контекст в Go устроен как древовидная структура, где каждый контекст может иметь одного родителя и множество дочерних элементов. Когда создается новый контекст с помощью функций WithCancel, WithDeadline, WithTimeout или WithValue, он наследует все свойства своего родительского контекста.

Внутри, контекст представляет собой интерфейс с несколькими методами:

  • Deadline() (deadline time.Time, ok bool): Возвращает время, когда работа должна быть завершена. Второе возвращаемое значение ok показывает, был ли установлен крайний срок.
  • Done() <-chan struct{}: Возвращает канал, который будет закрыт, когда работа должна быть отменена. Если канал закрыт, то Err() вернет не nil.
  • Err() error: Возвращает ошибку, которая описывает причину завершения контекста. Это может быть context.Canceled или context.DeadlineExceeded.
  • Value(key interface{}) interface{}: Возвращает значение, связанное с ключом. Если ключа нет, возвращается nil.

Когда контекст отменяется, все его дочерние контексты также отменяются. Это позволяет управлять группами горутин, которые выполняют связанные задачи. Если одна задача отменяется, все связанные задачи также отменяются

Как работает WithCancel?

Функция WithCancel из пакета context в Go создает новый контекст из существующего (родительского) контекста, который может быть отменен. Эта функция возвращает новый контекст и функцию cancel, которую можно вызвать, чтобы отменить контекст.

Вот как это работает:

1
ctx, cancel := context.WithCancel(parentCtx)

Здесь ctx - это новый контекст, который наследует все свойства от parentCtx, и cancel - это функция, которую можно вызвать, чтобы отменить ctx и все контексты, производные от ctx.

Когда функция cancel вызывается, канал Done контекста ctx закрывается. Все горутины, которые слушают канал Done, могут проверить его закрытие, чтобы узнать, был ли контекст отменен.

Важно всегда вызывать cancel в defer (или когда контекст больше не нужен), чтобы освободить ресурсы, связанные с контекстом. Если cancel не вызывается, то может произойти утечка ресурсов.

1
defer cancel() // Make sure to cancel when done with context

Перекидывали логгер в контексте?

Передача логгера через контекст - это тема, которая вызывает много дискуссий в сообществе Go. Вот некоторые аргументы “за” и “против”:

За:

  1. Простота: Передача логгера через контекст может упростить API, так как вам не нужно передавать логгер в каждую функцию.

  2. Передача метаданных: Если вы используете структурированное логирование, вы можете добавить метаданные (например, ID запроса) в логгер, который затем передается через контекст. Это позволяет автоматически включать эти метаданные во все сообщения лога.

Против:

  1. Неявность: Передача логгера через контекст делает его неявным. Это может затруднить понимание того, откуда идут сообщения лога.

  2. Неправильное использование контекста: Документация Go говорит, что контекст должен использоваться для передачи данных, которые должны быть доступны в течение жизненного цикла запроса, а не для передачи опциональных параметров функции. Некоторые люди считают, что передача логгера через контекст - это злоупотребление контекстом.

  3. Проблемы с производительностью: Создание нового контекста для каждого запроса с логгером может привести к увеличению накладных расходов и использованию памяти.

В конечном итоге, решение о том, передавать ли логгер через контекст, зависит от конкретных требований вашего приложения и вашего стиля программирования.

Вот вам и context

Поделиться