context.Context
Пакет context
в Go используется для управления временем выполнения и отмены операций, особенно при работе с горутинами, сетевыми запросами и базами данных. Он позволяет передавать сигналы отмены, дедлайны и другие значения через API без необходимости изменения сигнатур всех функций.
Основные концепции:
- Контекст (Context) – объект, который передаётся в функции и горутины, обеспечивая управление временем выполнения.
- Отмена (Cancelation) – позволяет принудительно завершить операции.
- Дедлайн (Deadline) – устанавливает максимальное время выполнения.
- Значения (Values) – можно передавать дополнительные данные в контексте.
type Context interface {
// Deadline возвращает время когда этот Context будет отменен.
Deadline() (deadline time.Time, ok bool)
// Done возвращает канал, который закрывается когда Context отменяется
Done() <-chan struct{}
// Err объясняет почему контекст был отменен, после того как закрылся канал Done.
Err() error
// Value возвращает значение ассоциированное с ключем или nil.
Value(key interface{}) interface{}
}
Код почти дословно иллюстрирует, для чего используется контекст:
- чтобы устанавливать дедлайн исполнения блока кода;
- оповещать об окончании исполнения блока кода;
- узнавать причину отмены контекста;
- получать значения по ключу.
Основные функции:
context.Background()
– создаёт базовый контекст, используется как корневой, следует использовать только на самом высоком уровне, как корень всех производных контекстов.context.TODO()
– заглушка, если ещё не ясно, какой контекст использовать.context.WithCancel(parent)
– создаёт новый контекст с возможностью отмены.context.WithTimeout(parent, duration)
– создаёт контекст с тайм-аутом.context.WithDeadline(parent, time)
– задаёт конкретное время дедлайна.context.WithValue(parent, key, value)
– создаёт контекст с дополнительными данными. Следует использовать как можно реже, и его нельзя применять для передачи необязательных параметров. Это делает API непонятным и может привести к ошибкам. Такие значения должны передаваться как аргументы.
Все методы безопасны для одновременного использования в разных go-рутинах.
Интерфейса достаточно для использования в любых местах, где код может «зависнуть». Это любое сетевое взаимодействие, а также долгие задачи, не выходящие за рамки процесса ОС. Кроме того, контекст можно использовать для неявной передачи параметров в функции. Хорошей практикой, считается "управлять" горутинами через контекст
Best practices
Передавайте context.Context
первым аргументом в функции
Такой порядок принят в стандартной библиотеке Go.
✅ Правильно:
func fetchData(ctx context.Context, url string) error {
❌ Неправильно:
func fetchData(url string, ctx context.Context) error {
Не сохраняйте context.Context
в структуре
Контексты живут ограниченное время, поэтому их нельзя хранить в полях структуры.
✅ Правильно:
func (s *Server) HandleRequest(ctx context.Context, req Request) {
❌ Неправильно:
type Server struct {
ctx context.Context
}
Не передавайте nil
в качестве контекста
Если контекст неизвестен, используйте context.TODO()
.
✅ Правильно:
ctx := context.TODO()
❌ Неправильно:
ctx := nil // вызовет панику
Используйте defer cancel()
для WithCancel()
, WithTimeout()
и WithDeadline()
Это гарантирует, что ресурсы контекста будут освобождены.
✅ Правильно:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
❌ Неправильно:
ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) // cancel() никогда не вызывается
Не передавайте контекст в struct{}
через WithValue()
Контекст предназначен для передачи метаданных, а не для конфигурации.
✅ Правильно:
ctx := context.WithValue(ctx, "userID", 123)
❌ Неправильно:
ctx := context.WithValue(ctx, "config", myStruct) // так делать не стоит
Не используйте context.Background()
в обработчиках запросов
Вместо него используйте контекст от сервера или родительский контекст.
✅ Правильно:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
}
❌ Неправильно:
func handler(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() // теряется связь с клиентом
}
Используйте select
для обработки ctx.Done()
в горутинах
Чтобы корректно завершать горутину, когда контекст отменяется.
✅ Правильно:
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Остановка по контексту")
return
default:
fmt.Println("Работаю...")
time.Sleep(500 * time.Millisecond)
}
}
}
❌ Неправильно:
func worker(ctx context.Context) {
for {
fmt.Println("Работаю...")
time.Sleep(500 * time.Millisecond)
}
}
Не используйте context.WithValue()
для передачи больших объектов
Контекст предназначен для передачи лёгких метаданных (например, userID
), а не больших структур.
✅ Правильно:
ctx := context.WithValue(ctx, "userID", 123)
❌ Неправильно:
ctx := context.WithValue(ctx, "user", largeUserStruct) // может привести к утечкам памяти
Дополнительно: