Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего контекст (Context) в Go
Context (контекст) — это один из фундаментальных паттернов в Go, позволяющий управлять отмены операций, временем ожидания и передавать значения через цепочку вызовов goroutines. Это критически важно для написания надёжного асинхронного кода.
Основные цели контекста
1. Управление отменой операций Контекст позволяет сигнализировать goroutines о необходимости прекратить работу.
import "context"
import "time"
func main() {
// Создаём контекст с отменой
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine отменена")
return
default:
fmt.Println("Работаю...")
time.Sleep(1 * time.Second)
}
}
}()
time.Sleep(3 * time.Second)
cancel() // Отменяем контекст
time.Sleep(1 * time.Second)
}
2. Управление временем ожидания (timeout) Контекст может автоматически отменяться через определённое время.
func fetchDataWithTimeout() {
ctx, cancel := context.WithTimeout(
context.Background(),
5 * time.Second,
)
defer cancel()
data, err := fetchData(ctx)
if err != nil {
fmt.Println("Timeout или ошибка:", err)
return
}
fmt.Println("Получены данные:", data)
}
func fetchData(ctx context.Context) (string, error) {
// Примерно 10 секунд работы
select {
case <-time.After(10 * time.Second):
return "data", nil
case <-ctx.Done():
return "", ctx.Err() // Вернёт context deadline exceeded
}
}
3. Передача значений через goroutines Контекст может хранить значения, доступные всем goroutines в цепочке вызовов.
type requestID string
const requestIDKey requestID = "requestID"
func handleRequest(parentCtx context.Context, userID string) {
// Добавляем значения в контекст
ctx := context.WithValue(parentCtx, requestIDKey, "req-12345")
// Передаём контекст в другие функции
processRequest(ctx)
}
func processRequest(ctx context.Context) {
// Достаём значение из контекста
reqID, ok := ctx.Value(requestIDKey).(string)
if ok {
fmt.Println("ID запроса:", reqID)
}
}
Иерархия контекстов
Контексты образуют иерархическое дерево отмены.
context.Background() ← root контекст
│
├─ WithCancel() ← можно отменить вручную
│ └─ WithTimeout() ← отмена через время
│
└─ WithTimeout() ← отмена через время
func main() {
// Родительский контекст
parent, parentCancel := context.WithCancel(context.Background())
defer parentCancel()
// Дочерний контекст наследует отмену
child, childCancel := context.WithCancel(parent)
defer childCancel()
// Если отменить parent, отменится и child
go func() {
<-parent.Done()
fmt.Println("Parent отменён, child тоже отменится")
}()
parentCancel()
time.Sleep(time.Second)
}
Практический пример: HTTP сервер
import "net/http"
func handleRequest(w http.ResponseWriter, r *http.Request) {
// r.Context() автоматически отменяется при разрыве соединения
ctx := r.Context()
// Добавляем timeout
ctx, cancel := context.WithTimeout(ctx, 30 * time.Second)
defer cancel()
// Выполняем долгую операцию
result := expensiveOperation(ctx)
w.Write([]byte(result))
}
func expensiveOperation(ctx context.Context) string {
done := make(chan string)
go func() {
// Долгая операция
time.Sleep(5 * time.Second)
done <- "результат"
}()
select {
case result := <-done:
return result
case <-ctx.Done():
return "Операция отменена или истёк timeout"
}
}
Best Practices
1. Никогда не сохраняй контекст как поле структуры
// ❌ Плохо
type Handler struct {
ctx context.Context // НИКОГДА!
}
// ✅ Хорошо
func (h *Handler) Handle(ctx context.Context) {
// Передавай как параметр
}
2. Передавай контекст первым параметром
// ✅ Правильный порядок
func processData(ctx context.Context, userID string, data []byte) error {
// ...
}
// ❌ Неправильный порядок
func processData(userID string, ctx context.Context, data []byte) error {
// ...
}
3. Проверяй Done канал в цикла обработки
func worker(ctx context.Context, jobs <-chan int) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker остановлен")
return
case job := <-jobs:
process(job)
}
}
}
4. Используй WithValue только для передачи данных, относящихся к запросу
// ✅ Правильное использование
ctx = context.WithValue(ctx, "user_id", 123)
ctx = context.WithValue(ctx, "request_id", "req-456")
// ❌ Неправильное использование
ctx = context.WithValue(ctx, "database_connection", db) // НЕ передавай ресурсы
Ошибки при работе с Context
// Ошибка 1: игнорирование отмены
func longRunningTask() {
for {
// ❌ Не проверяем контекст!
doSomeWork()
}
}
// Ошибка 2: забыли вызвать cancel
func badFunction() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
// ❌ забыли: defer cancel()
}
// Ошибка 3: создание контекста на основе значения вместо time.After
func wrong() {
ctx := context.WithValue(context.Background(), "timeout", "5s") // ❌
// Это не создаёт timeout!
}
Реальный пример: параллельные запросы с отменой
func fetchMultipleURLs(ctx context.Context, urls []string) []string {
results := make([]string, len(urls))
var wg sync.WaitGroup
for i, url := range urls {
wg.Add(1)
go func(i int, url string) {
defer wg.Done()
select {
case <-ctx.Done():
results[i] = "Отменено"
return
default:
data, _ := http.Get(url)
results[i] = data
}
}(i, url)
}
wg.Wait()
return results
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()
results := fetchMultipleURLs(ctx, []string{
"http://example.com",
"http://example.org",
"http://example.net",
})
fmt.Println(results)
}
Context — это необходимый инструмент для написания конкурентного, надёжного кода на Go. Он позволяет контролировать жизненный цикл goroutines и операций, предотвращая утечки ресурсов и обеспечивая graceful shutdown.