GoProg

 
Топ хэштегов


Архив

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

func DoFunc(ctx context.Context, arg Arg) error {
	// ... используем ctx ...
}

Интерфейс контекста имеет следующую структуру:

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

Метод Deadline возвращает ожидаемое время завершения работы и указывает, когда контекст должен быть отменен.

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

Err вернет nil, пока не будет закрыто Done. После чего Err либо вернет Cancelled, если контекст был отменен, либо DealineExceeded, если крайний срок контекста прошел.

Функция context.Background возвращает пустой контекст, отличный от нуля. Для этого контекста не определен дедлайн и нет отмены. Обычно Background используют в основной функции, для тестирования или для создания контекста верхнего уровня, который будет преобразован во что-то еще.

Новый контекст создается на базе уже существующего или на базе базового контекста, который можно получить методом context.Background . В зависимости от типа нужного нам контекста для создания нужно использовать один из следующих методов: WithCancel, WithDeadlne, WithTimeout, WithValue (каждый из этих методов первый принимает родительский контекст).

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

package main

import (
  "context"
  "fmt"
)

func main() {

  gen := func(ctx context.Context) <-chan int {
    dst := make(chan int)
    n := 1
    go func() {
      for {
        select {
        case <-ctx.Done():
          return // returning not to leak the goroutine
        case dst <- n:
          n++
        }
      }
    }()
    return dst
  }

  ctx, cancel := context.WithCancel(context.Background())
  defer cancel() // остановка после последнего числа

  for n := range gen(ctx) {
    fmt.Println(n)
    if n == 5 {
      break
    }
  }
}

Контекст можно использовать для задания дедлайна выполнения метода. Для этого нужно создать контекст при помощи метода WithDeadlne и передать ему время завершения (объект time.Time). Если контекст создается на базе контекста с уже заданным дедлайном и у родительского контекста дедлайн ближе, то сработает он. Контекст с дедлайном полезен когда внутри метода идет обращение к внешним ресурсам, время ответа которых может быть непредсказуемым. Пример использования ниже:

package main

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

func main() {
  d := time.Now().Add(50 * time.Millisecond)
  ctx, cancel := context.WithDeadline(context.Background(), d)
  defer cancel()

  select {
  case <-time.After(1 * time.Second):
    fmt.Println("overslept")
  case <-ctx.Done():
    fmt.Println(ctx.Err()) // напечатает ошибку
  }
}

Таймаут — это альтернатива дедлайну с той разницей, что при создании контекста (WithTimeout) уже передается не точное время завершения, а интервал времени выполнения (time.Duration) относительно текущего момента времени. Это удобнее чем самому для каждого вызова рассчитывать дедлай. Если у родительского контекста таймаут раньше то сработает он. Пример использования ниже:

package main

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

func main() {
  interval := 50*time.Millisecond
  ctx, cancel := context.WithTimeout(context.Background(), interval)
  defer cancel()

  select {
  case <-time.After(time.Second):
    fmt.Println("overslept")
  case <-ctx.Done():
    fmt.Println(ctx.Err()) // "context deadline exceeded"
  }
}

Контекст можно использовать для передачи параметром запроса. Для этого нужно создать контекст при помощи метода WithValue. Рекомендуется использовать в качестве типа ключа пользовательский тип. Ниже приведен пример, демонстрирующий передачу параметра через контекст. В этом примере функция f, проверяет есть ли в контексте параметр по заданному ключу:

package main

import (
  "context"
  "fmt"
)

func main() {
  type favContextKey string

  f := func(ctx context.Context, k favContextKey) {
    if v := ctx.Value(k); v != nil {
      fmt.Println("found value:", v)
      return
    }
    fmt.Println("key not found:", k)
  }

  k := favContextKey("language")
  ctx := context.WithValue(context.Background(), k, "Go")

  f(ctx, k)
  f(ctx, favContextKey("color"))
}

Для получения большей информации можно обратиться к официальному руководству.

#Go #golang #IT #programming #программирование



Новый комментарий: