- Оригинальный адрес:Контекст официальной документации
- Адрес перевода:GitHub.com/вода Мел О/…
- Переводчик: хаки хаки
- Уровень переводчика ограничен, если есть ошибка в переводе или понимании, помогите указать
На только что прошедшей конференции gopher china 2019 концепция контекста упоминалась много раз, в том числе в исходном коде многих фреймворков. Видно, что контекст — очень важная точка знаний в мире голанга, поэтому необходимо иметь базовое использование и понимание контекста. Объяснения и примеры официальных документов более подробные и формальные, а перевод углубит ваше понимание с точки зрения обучения.
Обзор
Пакет context определяет тип Context, который передает крайние сроки, сигналы отмены и другие значения в области запроса между границами API и процессами.
Когда служба получает запрос, она должна создать контекст, а ответ службе должен принять контекст. Цепочка вызовов функций между ними должна передавать контекст, который также можно заменить, создав производный контекст с помощью таких методов, как WithCancel, WithDeadline, WithTimeout или WithValue. Когда Контекст отменяется, все Контексты, производные от него, также отменяются.
Функции WithCancel, WithDeadline и WithTimeout принимают контекст (родительский) и возвращают производный контекст (дочерний) и функцию CancelFunc. Вызов функции CancelFunc отменяет производный дочерний контекст и его внучатый контекст, удаляет родительскую ссылку на дочерний элемент и останавливает все связанные таймеры. Если не вызвать CancelFunc, произойдет утечка потомков и внуков до тех пор, пока родитель не будет отменен или не сработает таймер. Инструмент go vet проверяет, используются ли CancelFuncs на всех путях потока управления.
Программы, использующие контексты, должны следовать этим правилам, чтобы интерфейс оставался согласованным между пакетами и чтобы инструменты статического анализа могли проверять доставку контекста:
Не храните контексты в типах структур; вместо этого явно передавайте контекст каждой функции, которая в нем нуждается. Контекст должен быть первым параметром, обычно называемым ctx:
func DoSomething(ctx context.Context, arg Arg) error {
// ... use ctx ...
}
Не передавайте nil Context, даже если функция это позволяет. Если вы не уверены, какой контекст использовать, передайте context.TODO.
Используйте значение контекста только для данных в области запроса между границами API и процессами, а не передавайте необязательные аргументы функциям.
Один и тот же контекст может быть передан функциям, работающим в разных горутинах; контексты безопасны для одновременного использования несколькими горутинами.
Пример кода, использующего контексты в службе, см.blog.golang.org/context.
index
Переменная
Canceled — это ошибка, возвращаемая Context.Err при отмене контекста.
var Canceled = errors.New("context canceled")
DeadLineExceeded — это ошибка, возвращаемая context.err, когда контекст превышает крайний срок.
var DeadlineExceeded error = deadlineExceededError{}
функцияWithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel возвращает родительскую копию с новым каналом Done. Канал Done возвращенного контекста закрывается, когда вызывается возвращаемая функция отмены, или закрывается канал Done родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста высвобождает связанные с ним ресурсы, поэтому код должен вызвать отмену, как только операция в этом контексте будет завершена.
Пример
Этот пример демонстрирует использование отменяемого контекста для предотвращения утечек goroutine. В конце примера функции горутина, запущенная gen, вернется без утечки горутины.
package main
import (
"context"
"fmt"
)
func main() {
// gen 在单独的 goroutine 中生成整数并将它们发送到返回的 channel。
// 一旦消费了生成的整数,gen 的调用者需要取消上下文,从而不会泄漏 gen 启动的内部 goroutine。
gen := func(ctx context.Context) <-chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case <-ctx.Done():
return // 返回以致不泄露 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
}
}
}
функцияWithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline возвращает копию родительского контекста с установленным крайним сроком не позднее d. WithDeadline(parent, d) семантически эквивалентен parent, если срок родителя предшествует d. Канал Done возвращенного контекста закрывается по истечении времени истечения, когда вызывается возвращенная функция отмены или когда закрывается канал Done родительского контекста, в зависимости от того, что произойдет раньше.
Отмена этого контекста освобождает связанные с ним ресурсы, поэтому код должен вызвать отмену, как только операция в этом контексте будет завершена.
Пример
В этом примере передается контекст с произвольным крайним сроком, чтобы сообщить блокирующей функции, что она должна отказаться от своей задачи, когда истечет время ожидания.
package main
import (
"context"
"fmt"
"time"
)
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// 即使 ctx 将要过期,在任何情况下要好也最调用它的取消函数。
// 如果不这样做,可能会使上下文及其父级的活动时间超过必要时间。
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}
функцияWithTimeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout возвращает WithDeadline(parent, time.Now().Add(timeout)).
Отмена этого контекста высвобождает связанные с ним ресурсы, поэтому код должен вызвать отмену, как только операции, выполняемые в этом контексте, будут завершены:
func slowOperationWithTimeout(ctx context.Context) (Result, error) {
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
defer cancel() // 如果 slowOperation 在超时之前完成,则释放资源
completes before timeout elapses
return slowOperation(ctx)
}
Пример
В этом примере контекст передается с тайм-аутом, чтобы сообщить блокирующей функции, что она должна отказаться от своей задачи по истечении тайм-аута.
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"
}
}
Типы CancelFunc
CancelFunc уведомляет операцию об отказе от ее задачи. CancelFunc не ожидает остановки задачи. После первого вызова последующие вызовы CancelFunc завершатся ошибкой.
type CancelFunc func()
Типы Context
Контекст может передавать сроки, сигналы отмены и другие значения через границы API.
Методы Context могут вызываться одновременно несколькими горутинами.
type Context interface {
// Deadline 返回完成的任务的时间,即取消此上下文的时间。
// 如果没有设置截止时间,Deadline 返回 ok == false。
// 对截止日期的连续调用返回相同的结果。
Deadline() (deadline time.Time, ok bool)
// 当任务完成时,即此上下文被取消,Done 会返回一个关闭的channel。
// 如果此上下文一直不被取消,Done 返回 nil。对 Done 的连续调用会返回相同的值。
//
// 当取消函数被调用时,WithCancel 使 Done 关闭;
// 在截止时间到期时,WithDeadline 使 Done 关闭;
// 当超时的时候,WithTimeout使 Done 关闭。
//
// Done 可以使用 select 语句:
//
// // Stream 使用 DoSomething 生成值并将它们发送到 out,
// // 直到 DoSomething 返回错误或 ctx.Done 关闭。
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// 查看 https://blog.golang.org/pipelines 获得更多关于怎么使用 Done channel 去取消的例子
Done() <-chan struct{}
// 如果 Done 尚未关闭,则 Err 返回 nil。
// 如果 Done 关闭,Err 会返回一个非nil的错误,原因:
// 如果上下文被取消,则调用 Canceled;
// 如果上下文的截止时间已过,则调用 DeadlineExceeded。
// 在 Err 返回非 nil 错误后,对 Err 的连续调用返回相同的错误。
Err() error
// Value 返回与此上下文关联的 key 的值,如果没有值与 key 关联,则返回nil。使用相同的 key 连续调用 Value 会返回相同的结果。
//
// 仅将上下文的值用于API边界和进程之间的请求作用域数据,而不是将可选参数传递给函数。
//
// key 标识上下文中的特定值。
// 在上下文中存储值的函数通常在全局变量中分配一个 key,然后使用该 key 作为 context.WithValue 和 Context.Value 的参数。
// key 可以是支持比较的任何类型
// 包应该将 key 定义为非导出类型以避免冲突。
//
// 定义 Context key 的包应该为使用该 key 存储的值提供类型安全的访问:
//
// // 包使用者定义一个存储在上下文中的 User 类型。
// package user
//
// import "context"
//
// // User 是上下文中值的类型。
// type User struct {...}
//
// // key 是此程序包中定义的 key 的非导出类型。
// // 这可以防止与其他包中定义的 key 冲突。
// type key int
//
// // userKey 是上下文中 user.User 值的 key。它是不可以被导出的。
// // 客户端使用 user.NewContext 和 user.FromContext 而不是直接使用 key。
// var userKey key
//
// // NewContext 返回一个带有值为 u 的新的上下文。
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext 返回存储在 ctx 中的 User 值(如果有的话)。
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
}
функцияBackground
func Background() Context
Фон возвращает ненулевой пустой контекст. Он никогда не отменяется, не имеет ценности и не имеет срока действия. Обычно он используется основной функцией для инициализации и тестирования, а также в качестве контекста верхнего уровня для запросов.
функцияTODO
func TODO() Context
TODO возвращает ненулевой пустой контекст. Код должен использовать context.TODO, когда неясно, какой контекст использовать или когда он еще недоступен (поскольку функция окружения не была расширена для приема параметра Context).
функцияWithValue
func WithValue(parent Context, key, val interface{}) Context
WithValue возвращает копию родителя со значением, связанным с ключом, как val.
Используйте значение контекста только для данных в области запроса между границами API и процессами, а не передавайте необязательные аргументы функциям.
Предоставленный ключ должен быть сравнимым и не должен иметь тип string или любой другой встроенный тип, чтобы избежать конфликтов между пакетами, использующими контекст. Потребители WithValue должны определить свой собственный пользовательский тип для ключей. Чтобы избежать указания при назначении interface{}, контекстные ключи обычно имеют конкретный тип struct {}. В качестве альтернативы статический тип ключевой переменной экспортируемого контекста должен быть указателем или интерфейсом.
Пример
В этом примере показано, как передать значение контексту и как получить его, если оно существует.
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"))
}