Контексты в Go — это способ переноса значений в области запроса и сигналов отмены через границы API. Они часто используются в Go, чтобы избежать передачи нескольких аргументов в функцию и отменить выполнение функции.

Зачем использовать контексты?

Одна из основных причин использования контекстов в Go — избежать передачи множественных аргументов в функцию. Рассмотрим следующий пример:

func doSomething(arg1 int, arg2 string, arg3 bool) {
    // Do some operation on the args
}


doSomething(1, "hello", true)

В этом примере мы передаем три аргумента функции doSomething. По мере увеличения количества аргументов может стать затруднительным отслеживать их все и гарантировать, что они передаются в правильном порядке.

Контексты позволяют группировать связанные значения и передавать их как один аргумент. Вот пример использования контекста:

type MyContext struct {
    Arg1 int
    Arg2 string
    Arg3 bool
}

func doSomething(ctx context.Context) {
    myCtx := ctx.Value("my_context").(MyContext)
    arg1 := myCtx.Arg1
    arg2 := myCtx.Arg2
    arg3 := myCtx.Arg3
}

myCtx := MyContext{1, "hello", true}
ctx := context.WithValue(context.Background(), "my_context", myCtx)
doSomething(ctx)

В этом примере мы создаем структуру с именем MyContext, содержащую три аргумента, которые мы хотим передать функции doSomething. Затем мы используем функцию context.WithValue для создания нового контекста со структурой MyContext в качестве значения. Наконец, мы передаем контекст функции doSomething и извлекаем значения из контекста внутри функции.

Отмена выполнения функции

Еще одна причина использовать контексты в Go — отменить выполнение функции. Рассмотрим следующий пример:

func doSomething() {
    // Do something that takes a long time
}


go doSomething()

В этом примере мы вызываем функцию doSomething в отдельной горутине. Если мы хотим отменить выполнение функции doSomething, мы можем использовать контекст с функцией отмены.

func doSomething(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            time.sleep(6000)
        }
    }
}


ctx, cancel := context.WithCancel(context.Background())
go doSomething(ctx)
// Cancel the function execution after 5 seconds
time.AfterFunc(5 * time.Second, cancel)

В этом примере мы используем функцию context.WithCancel для создания контекста с функцией отмены. Затем мы передаем контекст функции doSomething и используем оператор select для проверки канала Done контекста. Если канал Done закрыт, значит, контекст был отменен и мы возвращаемся из функции.

Мы также используем функцию time.AfterFunc для отмены контекста через 5 секунд.

Тайм-ауты

В дополнение к отмене выполнения функции контексты также могут использоваться для установки тайм-аута для функции. Это полезно, если вы хотите гарантировать, что функция не будет выполняться слишком долго.

Чтобы установить тайм-аут, вы можете использовать функцию context.WithTimeout:

ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
err := doSomething(ctx)
if err == context.DeadlineExceeded {
    // The function took too long
}

В этом примере мы используем функцию context.WithTimeout для создания контекста с таймаутом 5 секунд. Затем мы передаем контекст функции doSomething и проверяем ошибку, возвращаемую функцией. Если ошибка context.DeadlineExceeded, это означает, что функция заняла больше времени, чем время ожидания, и мы можем соответствующим образом обработать ошибку.

Пользовательские контексты

Помимо использования встроенных контекстов, предоставляемых пакетом context, вы также можете создавать свои собственные настраиваемые контексты. Это может быть полезно, если вы хотите сохранить дополнительные значения или добавить собственное поведение в свои контексты.

Чтобы создать собственный контекст, вы можете определить структуру, реализующую интерфейс context.Context:

type MyContext struct {
    context.Context
    Arg1 int
    Arg2 string
}

func (ctx MyContext) Value(key interface{}) interface{} {
    if key == "arg1" {
        return ctx.Arg1
    }
    if key == "arg2" {
        return ctx.Arg2
    }
    return ctx.Context.Value(key)
}

В этом примере мы определяем структуру с именем MyContext, которая включает интерфейс context.Context и добавляет два дополнительных поля: Arg1 и Arg2. Мы также реализуем метод Value интерфейса context.Context, чтобы разрешить доступ к значениям Arg1 и Arg2 через контекст.

Чтобы использовать пользовательский контекст, вы можете создать новый экземпляр MyContext и передать его функции:

myCtx := MyContext{context.Background(), 1, "hello"}
doSomething(myCtx)

Заключение

Контексты — полезный инструмент в Go для переноса значений в области запроса и сигналов отмены через границы API. Их можно использовать, чтобы избежать передачи нескольких аргументов в функцию и отменить выполнение функции. Контексты также можно использовать для установки тайм-аутов и создания настраиваемых контекстов с дополнительным поведением или значениями.

👋 Если вы считаете это полезным, пожалуйста, несколько раз нажмите кнопку аплодисментов 👏 ниже, чтобы выразить свою поддержку автору 👇

🚀Присоединяйтесь к сообществу разработчиков FAUN и получайте похожие истории в свой почтовый ящик каждую неделю