Как изящно отключить цепочечные программы идиоматическим способом - PullRequest
2 голосов
/ 05 апреля 2019

Создание нескольких подпрограмм, которые будут иметь вложенные подпрограммы при многоуровневой обработке (представьте себе дерево подпрограмм, каждый уровень может иметь много листьев).

Каков идиоматический способ изящно отключить эти программы для порядка и ждать, пока они вернутся? Порядок - это нижняя вершина (сначала самый глубокий дочерний элемент), а также предположим, что я не знаю, сколько процедур я запустлю заранее (динамически).

Пример ниже просто изящно закрывает их неупорядоченным образом.

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)

    //level1
    go func() {
        fmt.Println("level1 started")
        //level2
        go func() {
            fmt.Println("level2 started")

            //level3
            go func() {
                fmt.Println("level3 started")
                select {
                case <-ctx.Done():
                    fmt.Println("Done called on level3")
                case <-time.After(5* time.Second):
                    fmt.Println("After called on level3")
                }

            }()
            select {
            case <-ctx.Done():
                fmt.Println("Done called on level2")
            case <-time.After(7* time.Second):
                fmt.Println("After called on level2")
            }

        }()
        select {
        case <-ctx.Done():
            fmt.Println("Done called on level1")
        case <-time.After(10* time.Second):
            fmt.Println("After called on level1")
        }


    }()
    time.Sleep(1*time.Second)
    cancel()
    time.Sleep(1 * time.Second)
}

Ответы [ 2 ]

2 голосов
/ 05 апреля 2019

Чтобы дождаться группы горутин, sync.WaitGroup - идиоматическое решение.Вы можете добавить 1 к его счетчику при запуске новой программы (WaitGroup.Add()), и эта программа может сигнализировать, что это сделано с WaitGroup.Done().Родительская программа может вызвать WaitGroup.Wait(), чтобы дождаться завершения всех своих детей.

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

Вот как это применяется в вашем примере:

ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)

//level1
wg1 := &sync.WaitGroup{}
wg1.Add(1)
go func() {
    defer wg1.Done()
    fmt.Println("level1 started")
    //level2
    wg2 := &sync.WaitGroup{}
    wg2.Add(1)
    go func() {
        defer wg2.Done()
        fmt.Println("level2 started")

        //level3
        wg3 := &sync.WaitGroup{}
        wg3.Add(1)
        go func() {
            defer wg3.Done()
            fmt.Println("level3 started")
            select {
            case <-ctx.Done():
                fmt.Println("Done called on level3")
            case <-time.After(5 * time.Second):
                fmt.Println("After called on level3")
            }
            fmt.Println("Level 3 ended.")
        }()

        select {
        case <-ctx.Done():
            fmt.Println("Done called on level2")
        case <-time.After(7 * time.Second):
            fmt.Println("After called on level2")
        }
        wg3.Wait()
        fmt.Println("Level 2 ended.")
    }()

    select {
    case <-ctx.Done():
        fmt.Println("Done called on level1")
    case <-time.After(10 * time.Second):
        fmt.Println("After called on level1")
    }
    wg2.Wait()
    fmt.Println("Level 1 ended.")
}()

time.Sleep(1 * time.Second)
cancel()
wg1.Wait()
fmt.Println("Main ended.")

Thisвыходы (попробуйте на Go Playground ):

level1 started
level2 started
level3 started
Done called on level1
Done called on level3
Level 3 ended.
Done called on level2
Level 2 ended.
Level 1 ended.
Parent ended.

Что важно из вывода:

Level 3 ended.
Level 2 ended.
Level 1 ended.
Main ended.

Уровни заканчиваются в порядке убывания уровня (снизувверх), закрывается с "Main ended.".

0 голосов
/ 05 апреля 2019

Один из возможных, также я бы сказал, идиоматических, способов сделать это - передать канал strict{}. Всякий раз, когда вы хотите, чтобы указанная программа завершилась, просто напишите в этот канал пустую структуру: shutdown <- struct{}{}. Это должно сделать работу. В качестве альтернативы вы можете закрыть канал, вы узнаете это, указав false в качестве второго возвращаемого значения <-, но я бы рекомендовал использовать это только в том случае, если вам нужно поделиться этим каналом с несколькими программами. В общем, я нахожу этот подход немного дрянным и подверженным ошибкам.

С другой стороны: как в вашем примере выполняется отключение goroutines, после отмены контекста все goroutines вернутся. Не знаю, имеет ли это большую пользу в общем случае. Может быть, в вашем случае это так.

...