Понимание контекстов в Go

Изучение

Пакет Go contextэффективно обрабатывает отмены, таймауты и передачу данных между горутинами. Пакет позволяет функциям иметь больше «контекста» относительно среды, в которой они выполняются. Передача контекста с помощью вызовов функций может распространять контекстную информацию на протяжении всего жизненного цикла программы. Контекст обычно используется в средах, где управление жизненным циклом параллельных операций и их отмена имеют решающее значение, например в распределенных системах.

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

package main
import «fmt»
import «time»
func main() {
    for counter := 0; counter < 5; counter++ {
        go fmt.Println(counter)
    }
    //give some time for other goroutine to conclude
    time.Sleep(1 * time.Second)
}

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

Давайте начнем обсуждение контекстов с простого примера:

package main
import (
    «context»
    «fmt»
    «time»
)
func main() {
    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()
    go slowTask(ctx)
    //give some time for the slowTask goroutine to conclude
    time.Sleep(3 * time.Second)
}
func slowTask(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
        fmt.Println(«Operation complete.»)
    case <-ctx.Done():
        fmt.Println(«Canceled:», ctx.Err())
    }
}

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

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

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

Затем мы создаем контекст с помощью context.WithTimeout и переназначаем его переменной ctx. Как следует из названия, он создает контекст с таймаутом, в нашем случае 2 секунды. Тайм-аут указывает, что контекст будет отменен после тайм-аута.

Читайте также:  10 лучших строковых методов в Pandas

После этого мы запускаем горутину для выполнения задачи, и, как следует из названия, это будет медленная и трудоемкая задача. Рассмотрим два случая в slowTask функции. Либо операция завершается, либо время, выделенное контексту, истекает и отменяется. Вот как они обрабатываются:

  • Мы моделируем трудоемкую задачу, используя time.After. Если задача завершается успешно в течение трех секунд, мы печатаем соответствующее сообщение.
  • Функция ctx.Done()возвращает закрытый канал, когда контекст отменен или истекло время ожидания. В нашем случае контекст истечет через две секунды, и мы распечатаем сообщение об отмене с указанием причины, используя ctx.Err().

Создание контекстов в Go

Теперь, когда у нас есть базовое представление о контекстах, давайте официально представим некоторые варианты создания контекстов:

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

Передача значений через цепочку вызовов

Одним из распространенных вариантов использования пакета contextявляется передача пар ключ-значение через цепочку вызовов.

Рассмотрим случай, когда мы намереваемся передать некоторую информацию об авторизации, которую можно использовать для включения проверок авторизации в отдельные функции. Пример реализации context.WithValueпоказан ниже:

package main
import (
    «context»
    «fmt»
    «time»
)
type contextkey string
func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, contextkey(«AuthToken»), «X@cfs645JHSDcdae»)
    go processSensitiveData(ctx)
    //give some time for other goroutine to conclude
    time.Sleep(2 * time.Second)
}
func processSensitiveData(ctx context.Context) {
    authToken, ok := ctx.Value(contextkey(«AuthToken»)).(string)
    if !ok {
        //print appropriate message
        fmt.Println(«Access Denied — missing AuthToken!»)
        return
    }
    fmt.Printf(«Processing data using: %s», authToken)
}

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

Отмена цепочки вызовов функций

Мы также можем использовать contextпакет для отмены цепочки вызовов функций после определенного события. В приведенном ниже примере мы использовали context.WithCancel(ctx)новый производный контекст с cancelфункцией.

package main
import (
    «context»
    «fmt»
    «time»
)
func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
  go processOrder(ctx)
  time.Sleep(3 * time.Second)
  fmt.Println(«main: Canceling order processing!»)
  cancel()
  //give some time for other goroutines to conclude
  time.Sleep(4 * time.Second)
}
func processOrder(ctx context.Context) {
    fmt.Println(«Processing order…»)
    time.Sleep(1 * time.Second)
    GetOderDetails(ctx)
    // Check if cancelation signal received
    if ctx.Err() != nil {
        fmt.Println(«Canceled: Processing order»)
        return
    }
}
func GetOderDetails(ctx context.Context) {
    // Perform some work
    fmt.Println(«Fetching order details…»)
    // Simulate a long-running task
    time.Sleep(1 * time.Second)
    GetInventoryDetails(ctx)
    // Check if cancelation signal received
    if ctx.Err() != nil {
        fmt.Println(«Canceled: Fetching order details»)
        return
    }
    //continue work after fetching inventory details
}
func GetInventoryDetails(ctx context.Context) {
    // Perform some work
    fmt.Println(«Fetching inventory details…»)
    // Simulate a long-running task
    time.Sleep(2 * time.Second)
    // Check if cancelation signal received
    if ctx.Err() != nil {
        fmt.Println(«Canceled: Fetching inventory details»)
        return
    }
}

В приведенном выше примере используются различные концепции, изученные в блоге. Сначала mainфункция создает контекст и передает его processOrderфункции в строке 11, и мы видим, что контекст передается другим функциям в цепочке вызовов. Важно помнить, что при использовании context.WithCancelтолько функция, создающая контекст, может использовать cancelфункцию для отмены контекста и любых контекстов, производных от него.

Мы использовали time.Sleepимитацию обработки, и в нашем случае mainфункция отменяет контекст в строке 17 во время извлечения данных инвентаризации. Когда контекст передается функции, она должна учитывать его, проверяя его отмену. Существует более элегантный способ справиться с этим, используя select, channelsи отдельную горутину для одновременного опроса сообщения об отмене. Оставляем это вам в качестве упражнения!

Заключение

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

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

Оцените статью
bestprogrammer.ru
Добавить комментарий