фон контекста
Из-за горутины параллелизм в go очень удобен, но это также порождает другую проблему: когда мы выполняем трудоемкую асинхронную операцию, как завершить операцию в оговоренное время и вернуть пользовательский результат? Это также то, как мы часто говорим, как завершить горутин (поскольку горутина отличается от потока ОС, здесь нет активного механизма прерывания), вот сегодняшний контекст главного героя.
Контекст был создан Google и добавлен в стандартную библиотеку в версии 1.7. Согласно официальному документу, это глобальный контекст для запроса, несущий такие сигналы, как крайние сроки и ручная отмена, и содержит параллельную и безопасную карту для переноса данных. API контекста относительно прост, и далее я представлю его в конкретных сценариях использования.
Сценарий 1: Запросить передачу значения ссылки
Вообще говоря, наш корневой контекст будет построен следующим образом в точке входа запроса
ctx := context.Background()
Если вы не уверены, нужен ли вам глобальный контекст, вы можете использовать следующую функцию для построения
ctx := context.TODO()
но не ноль.
Способ передачи значения следующий
package main
import (
"context"
"fmt"
)
func func1(ctx context.Context) {
ctx = context.WithValue(ctx, "k1", "v1")
func2(ctx)
}
func func2(ctx context.Context) {
fmt.Println(ctx.Value("k1").(string))
}
func main() {
ctx := context.Background()
func1(ctx)
}
Мы назначаем k1 как v1 через WithValue(parent Context, key, val interface{}) Context в func1 и получаем значение k1 через ctx.Value(key interface{}) interface{} в его нижней функции func2, что относительно просто. Вот вопрос, если я присвою значение в func2, смогу ли я получить это значение в func1? Ответ — нет, контекст может передавать значения только сверху вниз, на это следует обратить внимание.
Используйте сценарий 2. Отмените трудоемкие операции и вовремя освободите ресурсы.
Рассмотрим такой вопрос, если нет пакета контекста, как мы можем отменить трудоемкую операцию? Я смоделировал два способа письма здесь
- В сценариях сетевого взаимодействия отмена тайм-аута часто выполняется через SetReadDeadline, SetWriteDeadline и SetDeadline.
timeout := 10 * time.Second
t = time.Now().Add(timeout)
conn.SetDeadline(t)
- Сценарии трудоемких операций, моделируемые методом выбора
package main
import (
"errors"
"fmt"
"time"
)
func func1() error {
respC := make(chan int)
// 处理逻辑
go func() {
time.Sleep(time.Second * 3)
respC <- 10
close(respC)
}()
// 超时逻辑
select {
case r := <-respC:
fmt.Printf("Resp: %d\n", r)
return nil
case <-time.After(time.Second * 2):
fmt.Println("catch timeout")
return errors.New("timeout")
}
}
func main() {
err := func1()
fmt.Printf("func1 error: %v\n", err)
}
Вышеупомянутые два метода также часто используются в инженерной практике.Давайте посмотрим, как использовать контекст для активной отмены, отмены тайм-аута и как работать с несколькими тайм-аутами.
- Активно отменить
package main
import (
"context"
"errors"
"fmt"
"sync"
"time"
)
func func1(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done()
respC := make(chan int)
// 处理逻辑
go func() {
time.Sleep(time.Second * 5)
respC <- 10
}()
// 取消机制
select {
case <-ctx.Done():
fmt.Println("cancel")
return errors.New("cancel")
case r := <-respC:
fmt.Println(r)
return nil
}
}
func main() {
wg := new(sync.WaitGroup)
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func1(ctx, wg)
time.Sleep(time.Second * 2)
// 触发取消
cancel()
// 等待goroutine退出
wg.Wait()
}
- тайм-аут отменить
package main
import (
"context"
"fmt"
"time"
)
func func1(ctx context.Context) {
hctx, hcancel := context.WithTimeout(ctx, time.Second*4)
defer hcancel()
resp := make(chan struct{}, 1)
// 处理逻辑
go func() {
// 处理耗时
time.Sleep(time.Second * 10)
resp <- struct{}{}
}()
// 超时机制
select {
// case <-ctx.Done():
// fmt.Println("ctx timeout")
// fmt.Println(ctx.Err())
case <-hctx.Done():
fmt.Println("hctx timeout")
fmt.Println(hctx.Err())
case v := <-resp:
fmt.Println("test2 function handle done")
fmt.Printf("result: %v\n", v)
}
fmt.Println("test2 finish")
return
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
func1(ctx)
}
Для обработки нескольких тайм-аутов вы можете открыть комментарии в приведенном выше примере отмены тайм-аута. Будет замечено, что когда обрабатываются два ctx, сначала будет запущено более короткое время. В этом случае, если только один контекст Done() тоже можно, но обязательноВызов двух функций отмены
Меры предосторожности
- Контекст может передавать значения только сверху вниз, а не наоборот.
- Если есть отмена, обязательно позвоните, иначе это вызовет утечку ресурсов, например, утечку таймера.
- Контекст не должен быть равен нулю.Если вы не уверены, вы можете использовать context.TODO() для создания пустого контекста.
Вышеупомянутое является первой частью анализа контекста, в основном с уровня использования, чтобы у каждого было интуитивное понимание, чтобы его можно было гибко использовать в проекте, а затем он будет проанализирован с уровня исходного кода.