Ответ
context.Context в Go — это стандартный механизм для передачи дедлайнов, сигналов отмены и других данных (request-scoped values) между горутинами. Он является неотъемлемой частью написания надежного и управляемого конкурентного кода, особенно в сетевых сервисах и распределенных системах.
Основные сценарии использования:
-
Отмена (Cancellation): Если пользователь отменил запрос (например, закрыл вкладку браузера),
contextпозволяет каскадно передать сигнал отмены всем горутинам, которые обрабатывали этот запрос (например, запросы к БД, другим сервисам), чтобы они прекратили работу и освободили ресурсы.- Создается с помощью
context.WithCancel(). - Проверяется через канал
<-ctx.Done().
- Создается с помощью
-
Таймауты и Дедлайны (Timeouts and Deadlines): Можно установить максимальное время на выполнение операции. Если время истекло, контекст будет отменен. Это критически важно для предотвращения "зависания" горутин в ожидании ответа от медленного внешнего сервиса.
- Создается с помощью
context.WithTimeout()илиcontext.WithDeadline().
- Создается с помощью
-
Передача данных (Value Propagation): Позволяет передавать данные вниз по стеку вызовов, например, ID запроса, информацию для трассировки или данные о пользователе.
- Создается с помощью
context.WithValue(). - Важно: Считается плохой практикой использовать
WithValueдля передачи обязательных параметров функции. Параметры должны передаваться явно. Контекст предназначен только для сквозных, request-scoped данных.
- Создается с помощью
Пример с таймаутом:
package main
import (
"context"
"fmt"
"time"
)
func longOperation(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("Операция успешно завершена")
case <-ctx.Done(): // Этот канал закроется при отмене контекста
fmt.Println("Операция прервана по таймауту:", ctx.Err())
}
}
func main() {
// Создаем контекст с таймаутом в 2 секунды
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Важно вызывать cancel, чтобы освободить ресурсы
longOperation(ctx) // Операция будет прервана через 2 секунды
}
Правила использования Context:
- Передавайте
Contextкак первый аргумент функции, с именемctx. - Никогда не храните
Contextвнутри структуры. Передавайте его явно в каждый вызов. - Цепочка вызовов должна пробрасывать контекст дальше.
- Никогда не передавайте
nilContext. Если не уверены, какой использовать, используйтеcontext.Background().
Ответ 18+ 🔞
Слушай, а вот этот твой context.Context в Go — это ж, блядь, такая хитрая жопа, которая по всему твоему коду бегает и всем передаёт, что пора закругляться, потому что пользователь уже в ротберунчик ушёл и вкладку закрыл. Представь себе: ты пишешь какой-нибудь микросервис, а там дохуя горутин на каждый запрос — одна к базе лезет, другая в другой сервис стучится, третья там хуй знает что делает. И вот если клиент свалил, а они все ещё работают — это пиздец, ресурсы жрут, память занимают. Вот контекст как раз и кричит им: «Э, сука, расслабьтесь, уже никому не надо!».
Зачем он, этот ёперный театр, вообще нужен?
- Отмена (Cancellation): Это когда ты говоришь всем своим подчинённым горутинам: «Всё, ребята, пиздуй отсюда, работа отменяется!». Например, пользователь нажал «стоп» или соединение разорвалось. Создаёшь контекст с помощью
context.WithCancel(), получаешь функциюcancel(). Вызвал её — и всем, кто этот контекст слушает, прилетает сигнал в<-ctx.Done(). - Таймауты и дедлайны (Timeouts and Deadlines): А это когда ты говоришь: «У тебя есть ровно две секунды, чтобы сделать своё дело, иначе я тебя сам выключу, пидарас шерстяной». Не хочешь, чтобы твой сервис вечно ждал ответа от медленной как черепаха базы данных.
context.WithTimeout()илиcontext.WithDeadline()— твои лучшие друзья. - Передача данных (Value Propagation): Ну, типа, передать какие-то служебные штуки вглубь вызовов. ID запроса для логов, данные пользователя для авторизации.
context.WithValue()для этого. Но, блядь, смотри — это не панацея! Не начинай туда пихать все параметры функций, а то получится мудя нечитаемая. Только для действительно сквозных, request-scoped данных. Обязательные аргументы — передавай явно, нормальным способом.
Вот смотри, живой пример с таймаутом, чтобы было понятно:
package main
import (
"context"
"fmt"
"time"
)
func longOperation(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("Операция успешно завершена")
case <-ctx.Done(): // Сюда сигнал прилетит, если контекст отменят
fmt.Println("Операция прервана по таймауту:", ctx.Err())
}
}
func main() {
// Дали операции 2 секунды. Не успела — в пизду.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Это ОБЯЗАТЕЛЬНО вызывать, даже если таймаут сам сработает. Ресурсы освободить, блядь!
longOperation(ctx) // Она попытается работать 3 секунды, но мы её вырубим через 2.
}
А теперь, блядь, главные правила, чтобы не выстрелить себе в ногу:
- Контекст всегда передавай первым аргументом в функцию. И называй его
ctx. Это как священная корова, все так делают. - Никогда, слышишь, НИКОГДА не засовывай контекст в поля какой-нибудь структуры. Он должен пробрасываться явно, по цепочке вызовов, как горячая картошка.
- Если функция принимает контекст — ты обязан его пробросить дальше, во все вызовы, которые его тоже принимают.
- Если тебе не с чем работать, используй
context.Background().nilконтекст передавать — это высший пилотаж идиотизма, на такое даже смотреть больно.
Вот и весь сказ, блядь. Освоишь контекст — будешь писать код, который не зависает нахуй и адекватно реагирует на внешний мир. А не будет как та Муму, которую в озеро бросили.