УДИВИТЕЛЬНАЯ ГОТОВНОСТЬ - # 2

Управляйте дочерними горутинами как босс с помощью context.Context

Если вы когда-либо работали с Go, вы, вероятно, поймете, что встроенная современная модель программирования с параллелизмом является одной из лучших особенностей Go и выделяет его среди других языков программирования. Написание кода параллелизма в Go на первый взгляд выглядит очень просто, просто выполните:

go task1()
go task2()

Тогда у вас есть ваши основные горутины, task1 и task2, работающие одновременно, и вам вообще не нужно заботиться о потоках. Go сделает все возможное, чтобы эффективно использовать ресурсы для вас.

Синхронизация тоже проста. С моделью программирования каналов просто общайтесь (обменивайтесь сообщениями) через каналы. Ваши горутины будут легко синхронизироваться, что называется «не позволяйте вычислениям взаимодействовать, разделяя память, позвольте им совместно использовать память, общаясь по каналам».

Однако, когда дело доходит до управления несколькими горутинами, Go больше не поддерживает все "из коробки". Когда ваша основная горутина завершается, другие горутины остаются запущенными, и вы несете ответственность за их очистку. Go имеет встроенные пакеты context и sync для взаимодействия с каналами для управления горутинами, но для достижения таких вариантов использования требуется небольшое исследование и понимание.

context.Context

Как описано в официальной документации Go, context.Context переносит крайние сроки, сигналы отмены и другие заданные значения через границы и между процессами.

Основное использование

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

// Task is a function that can be and should be run as goroutine,
// it can be cancelled by cancelling the input context.
func Task(context.Context, ...args interface{})

Это означает, что вы можете использовать функцию следующим образом:

func main() {
    // Create a new context being cancelled in 5 seconds.
    ctx, _ := context.WithTimeout(context.Background(), 5 * time.Second)
    // Start a new goroutine whose lifetime's attached to ctx.
    go task(ctx, args...)
}

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

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

Создание и извлечение контекста

Чтобы создать новый и пустой контекст, используйте:

// Create a new, empty, and unexecuted context.
context.Background()

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

func main() {
    // Create a new context.
    parent, cancelParent := context.WithCancel(context.Background())
    // Derive child contexts from parent.
    childA, _ := context.WithTimeout(parent, 5 * time.Secound)
    childB, _ := context.WithDeadline(parent, time.Now().Add(1 * time.Minute)
    go func() {
        <-childA.Done()
        <-childB.Done()
        fmt.Println("All children are done")
    }()
    // Cancel parent make all children are cancelled.
    cancelParent()
}
// -> Result: All children are done
  • context.WithCancel(parentContext) создает новый контекст, который завершается, когда вызывается возвращенная функция отмены или когда завершается родительский контекст, в зависимости от того, что произойдет раньше.
  • context.WithTimeout(contextContext, 5 * time.Second) создает новый контекст, который завершается, когда вызывается возвращенная функция отмены, или когда он превышает тайм-аут, или когда заканчивается родительский контекст, в зависимости от того, что произойдет раньше.
  • context.WithDeadline(parentContext, time.Now().Add(1 * time.Minute) создает новый контекст, который завершается, когда истекает срок возврата возвращаемой функции отмены или когда завершается родительский контекст, в зависимости от того, что произойдет раньше.

Есть и другие методы получения контекста. Ознакомьтесь с подробностями здесь.

Управление дочерними горутинами

А теперь давайте решим обычную проблему с помощью context.Context.

Ниже приведен очень распространенный вариант использования в параллельном программировании:

«У вас есть 2 задачи A и B. Ваша основная горутина разветвляет N горутин (рабочих), выполняющих задачу A, и M горутин (рабочих), выполняющих задачу B. закрыть приложение)? »