Когда горутина может запускать другие горутины, и эти горутины запускают другие горутины и т. Д., Тогда первая горутина должна иметь возможность отправлять сигналы отмены всем встроенным горутинам.

Единственная цель контекстного пакета - выполнять сигналы отмены между горутинами, независимо от того, как они были сгенерированы.

Интерфейс контекста определяется как:

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!!!!")
}

Также есть несколько примечаний о контексте:

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