Когда горутина может запускать другие горутины, и эти горутины запускают другие горутины и т. Д., Тогда первая горутина должна иметь возможность отправлять сигналы отмены всем встроенным горутинам.
Единственная цель контекстного пакета - выполнять сигналы отмены между горутинами, независимо от того, как они были сгенерированы.
Интерфейс контекста определяется как:
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <- chan struct{} Err() error Value(key interface{}) interface{} }
- Крайний срок: первое значение - это крайний срок, по истечении которого Контекст автоматически инициирует действие Отмена. Второе значение - логическое значение, true означает, что крайний срок установлен, false означает, что крайний срок не установлен. Если крайний срок не установлен, необходимо вручную вызвать функцию отмены, чтобы отменить контекст.
- Готово: вернуть канал только для чтения (только после отмены), введите struct {}, когда этот канал доступен для чтения, это означает, что родительский контекст инициировал запрос отмены, в соответствии с этим сигналом разработчик может выполнить некоторые действия по очистке, выйти из горутины
- Err: возвращает причину, по которой контекст был отменен
- Значение: возвращает значение, привязанное к контексту, это пара ключ-значение, поэтому вам нужно передать ключ, чтобы получить соответствующее значение, это значение является потокобезопасным
Чтобы создать контекст, вы должны указать родительский контекст.
Два встроенных контекста, фон и задача, служат родительским контекстом верхнего уровня:
var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }
Фон, который в основном используется в основной функции, инициализации и тестовом коде, является контекстом верхнего уровня древовидной структуры, корневым контекстом, который не может быть отменен.
СОДЕРЖАНИЕ, когда вы не знаете, какой контекст использовать , вы можете использовать это.
Оба они по существу относятся к типу emptyCtx, оба не подлежат отмене, ни один из них не имеет установленного срока, и ни один из них не несет никакого значения для контекста:
type emptyCtx int func (_ *emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (_ *emptyCtx) Done() <- chan struct{} { return nil } func (_ *emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil }
Пакет context также имеет несколько общих функций:
- func WithCancel (родительский контекст) (контекст ctx, отменить CancelFunc)
- func WithDeadline (родительский контекст, время крайнего срока.Time) (Context, CancelFunc)
- func WithTimeout (родительский контекст, время ожидания. Продолжительность) (Context, CancelFunc)
- func WithValue (родительский контекст, ключ, интерфейс val {}) Контекст
Обратите внимание, что эти методы означают, что контекст может быть унаследован один раз для достижения еще одной функции, например, используя функцию WithCancel для передачи в корневой контекст, он создает дочерний контекст, который имеет дополнительную функцию контекста отмены, а затем используйте это context (context01) в качестве родительского контекста и передать его в качестве первого параметра функции WithDeadline, будет получен дочерний контекст (context02) по сравнению с дочерним контекстом (context01). Он имеет дополнительную функцию для автоматической отмены контекста после последний срок.
WithCancel
Для канала, хотя канал также может уведомлять множество вложенных горутин о выходе, канал не является потокобезопасным, а контекст - потокобезопасным.
Например:
package main import ( "runtime" "fmt" "time" "context" ) func monitor2(ch chan bool, index int) { for { select { case v := <- ch: fmt.Printf("monitor2: %v, the received channel value is: %v, ending\n", index, v) return default: fmt.Printf("monitor2: %v in progress...\n", index) time.Sleep(2 * time.Second) } } } func monitor1(ch chan bool, index int) { for { go monitor2(ch, index) select { case v := <- ch: // this branch is only reached when the ch channel is closed, or when data is sent(either true or false) fmt.Printf("monitor1: %v, the received channel value is: %v, ending\n", index, v) return default: fmt.Printf("monitor1: %v in progress...\n", index) time.Sleep(2 * time.Second) } } } func main() { var stopSingal chan bool = make(chan bool, 0) for i := 1; i <= 5; i = i + 1 { go monitor1(stopSingal, i) } time.Sleep(1 * time.Second) // close all gourtines close(stopSingal) // waiting 10 seconds, if the screen does not display <monitorX: xxxx in progress...>, all goroutines have been shut down time.Sleep(10 * time.Second) println(runtime.NumGoroutine()) println("main program exit!!!!") }
Результат исполнения:
monitor1: 5 in progress... monitor2: 5 in progress... monitor1: 2 in progress... monitor2: 2 in progress... monitor2: 1 in progress... monitor1: 1 in progress... monitor1: 4 in progress... monitor1: 3 in progress... monitor2: 4 in progress... monitor2: 3 in progress... monitor1: 4, the received channel value is: false, ending monitor1: 3, the received channel value is: false, ending monitor2: 2, the received channel value is: false, ending monitor2: 1, the received channel value is: false, ending monitor1: 1, the received channel value is: false, ending monitor2: 5, the received channel value is: false, ending monitor2: 3, the received channel value is: false, ending monitor2: 3, the received channel value is: false, ending monitor2: 4, the received channel value is: false, ending monitor2: 5, the received channel value is: false, ending monitor2: 1, the received channel value is: false, ending monitor1: 5, the received channel value is: false, ending monitor1: 2, the received channel value is: false, ending monitor2: 2, the received channel value is: false, ending monitor2: 4, the received channel value is: false, ending 1 main program exit!!!!
Здесь канал используется для отправки уведомлений о завершении для всех горутин, но здесь ситуация относительно простая, если в сложном проекте предположить, что несколько горутин имеют какую-то ошибку и выполняются повторно, тогда можно повторно закрыть канал или закрыть канал, а затем записать в него значения, тем самым вызвав панику во время выполнения. Вот почему мы используем контекст, чтобы избежать этих проблем, на примере WithCancel:
package main import ( "runtime" "fmt" "time" "context" ) func monitor2(ctx context.Context, number int) { for { select { case v := <- ctx.Done(): fmt.Printf("monitor: %v, the received channel value is: %v, ending\n", number,v) return default: fmt.Printf("monitor: %v in progress...\n", number) time.Sleep(2 * time.Second) } } } func monitor1(ctx context.Context, number int) { for { go monitor2(ctx, number) select { case v := <- ctx.Done(): // this branch is only reached when the ch channel is closed, or when data is sent(either true or false) fmt.Printf("monitor: %v, the received channel value is: %v, ending\n", number, v) return default: fmt.Printf("monitor: %v in progress...\n", number) time.Sleep(2 * time.Second) } } } func main() { var ctx context.Context = nil var cancel context.CancelFunc = nil ctx, cancel = context.WithCancel(context.Background()) for i := 1; i <= 5; i = i + 1 { go monitor1(ctx, i) } time.Sleep(1 * time.Second) // close all gourtines cancel() // waiting 10 seconds, if the screen does not display <monitor: xxxx in progress>, all goroutines have been shut down time.Sleep(10 * time.Second) println(runtime.NumGoroutine()) println("main program exit!!!!") }
WithTimeout и WithDeadline
WithTimeout и WithDeadline в основном одинаковы с точки зрения использования и функций, они оба указывают на то, что контекст будет автоматически отменен через определенное время, единственную разницу можно увидеть из В определении функции второй параметр, переданный в WithDeadline, относится к типу time.Duration, который является относительным временем, означающим, сколько времени прошло после отмены тайм-аута.
Пример:
package main import ( "runtime" "fmt" "time" "context" ) func monitor2(ctx context.Context, index int) { for { select { case v := <- ctx.Done(): fmt.Printf("monitor2: %v, the received channel value is: %v, ending\n", index, v) return default: fmt.Printf("monitor2: %v in progress...\n", index) time.Sleep(2 * time.Second) } } } func monitor1(ctx context.Context, index int) { for { go monitor2(ctx, index) select { case v := <- ctx.Done(): // this branch is only reached when the ch channel is closed, or when data is sent(either true or false) fmt.Printf("monitor1: %v, the received channel value is: %v, ending\n", index, v) return default: fmt.Printf("monitor1: %v in progress...\n", index) time.Sleep(2 * time.Second) } } } func main() { var ctx01 context.Context = nil var ctx02 context.Context = nil var cancel context.CancelFunc = nil ctx01, cancel = context.WithCancel(context.Background()) ctx02, cancel = context.WithDeadline(ctx01, time.Now().Add(1 * time.Second)) // If it's WithTimeout, just change this line to "ctx02, cancel = context.WithTimeout(ctx01, 1 * time.Second)" defer cancel() for i := 1; i <= 5; i = i + 1 { go monitor1(ctx02, i) } time.Sleep(5 * time.Second) if ctx02.Err() != nil { fmt.Println("the cause of cancel is: ", ctx02.Err()) } println(runtime.NumGoroutine()) println("main program exit!!!!") }
WithValue
Некоторые требуемые метаданные также могут быть переданы через контекст, который добавляется к контексту для использования.
Метаданные передаются как ключ-значение, но обратите внимание, что ключ должен быть сопоставимым, а значение должно быть потокобезопасным.
package main import ( "runtime" "fmt" "time" "context" ) func monitor(ctx context.Context, index int) { for { select { case <- ctx.Done(): // this branch is only reached when the ch channel is closed, or when data is sent(either true or false) fmt.Printf("monitor %v, end of monitoring. \n", index) return default: var value interface{} = ctx.Value("Nets") fmt.Printf("monitor %v, is monitoring %v\n", index, value) time.Sleep(2 * time.Second) } } } func main() { var ctx01 context.Context = nil var ctx02 context.Context = nil var cancel context.CancelFunc = nil ctx01, cancel = context.WithCancel(context.Background()) ctx02, cancel = context.WithTimeout(ctx01, 1 * time.Second) var ctx03 context.Context = context.WithValue(ctx02, "Nets", "Champion") // key: "Nets", value: "Champion" defer cancel() for i := 1; i <= 5; i = i + 1 { go monitor(ctx03, i) } time.Sleep(5 * time.Second) if ctx02.Err() != nil { fmt.Println("the cause of cancel is: ", ctx02.Err()) } println(runtime.NumGoroutine()) println("main program exit!!!!") }
Также есть несколько примечаний о контексте:
- Не храните контексты в типах структур, а явно передавайте контекст каждой функции, которая в нем нуждается, и контекст должен быть первым аргументом.
- Не передавайте нулевой контекст, даже если функция позволяет это, или если вы не уверены, какой контекст использовать, передавайте контекст.
- Не передавайте переменные, которые могут быть переданы как аргументы функции в значение контекста.