context.Context: отмена и таймаут по цепочке вызовов
Содержание курса
В прошлом уроке stopCh — закрытый канал — служил сигналом остановки горутин. Он прекрасно справлялся с одной функцией, но как только вызовов становится несколько, нужно вручную прокидывать stopCh по всей цепочке и синхронизировать закрытие. context.Context решает именно эту задачу: отмена распространяется по дереву вызовов автоматически, а тип и соглашения стандартизированы.
context.Background() как корень и соглашение о передаче ctx первым аргументом
context.Background() — это отправная точка. Он не имеет дедлайна, никогда не отменяется и не несёт никаких значений. Его единственная роль — быть корнем, от которого вы потом получите дочерние контексты через WithCancel или WithTimeout.
В main это выглядит так:
package main
import (
"context"
"fmt"
)
func doWork(ctx context.Context, name string) {
fmt.Println("working:", name)
}
func main() {
ctx := context.Background()
doWork(ctx, "task-1")
}
Обратите внимание на сигнатуру doWork: ctx context.Context идёт первым параметром, тип — интерфейс context.Context, а не какой-либо конкретный тип. Это устоявшееся соглашение Go: если функция принимает контекст, он обычно передаётся первым параметром и называется ctx. Стандартная библиотека и большинство популярных пакетов придерживаются этого правила, поэтому функцию можно вызвать с любым контекстом — фоновым, с таймаутом или тестовым.
Почему ctx не хранят в поле структуры? Контекст описывает время жизни конкретного вызова или запроса: у каждого HTTP-запроса свой ctx, который отменяется, когда клиент отключился. Если поместить контекст в поле, он теряет эту привязку: непонятно, когда его отменять, кто владеет его жизненным циклом и откуда пришёл сигнал отмены. Это делает код непрозрачным и трудным для тестирования. Передача через аргумент явно показывает: «этот контекст действует только во время данного вызова».
