Go: context

Пакет 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{}

  ...
}

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

Новый контекст создается на базе уже существующего или на базе базового контекста, который можно получить методом 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() {
  ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  defer cancel()

  select {
  case <-time.After(1 * 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"))

}

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

 

20.09.2018









 
архив

подписка