Базовая c процедура и схема канала: несколько процедур для одного канала - PullRequest
0 голосов
/ 20 апреля 2020

Я новичок в Go и задаюсь вопросом о некоторой довольно простой проблеме c, которую я не могу ясно понять.

Только для упражнения (абстракция реальной потребности), мне нужно:

  • инициализировать фрагмент строки с количеством элементов, зафиксированных константой ITERATIONS
  • , выполнить итерацию по этому фрагменту и запустить goroutine для каждого элемента
  • каждая процедура займет определенное количество времени для обработки элемента (случайная длительность секунды)
  • как только работа будет завершена, я хочу, чтобы программа обработала pu sh результат в канал
  • затем мне нужно перехватить все результаты с этого канала (в функции, вызываемой из главной процедуры), добавить их к последнему фрагменту и затем, когда он закончится
  • распечатать длину финального нарезать и добавить немного основы c отслеживания времени

Ниже приведен код, который работает.

Не удивительно, что общее количество времени программы всегда более или менее равно значению const MAX_SEC_SLEEP, поскольку все процедуры обработки работают параллельно.

Но как насчет:

  1. получающая часть:

действительно ли мне нужно обернуть свой оператор select в a для l oop, повторяя точное количество ITERATIONS, чтобы иметь точно такое же число приемники, чем количество горутин, которые будут заканчиваться на канале? Это единственный способ избежать тупика здесь? А что, если по какой-то причине один из goroutine потерпит неудачу?

Я не могу найти способ иметь простую (когда-либо) l oop обертку выбора, с двумя случаями (один получает от канал результатов и еще один, такой как case <-done, который будет возвращен из функции). Будет ли это лучший паттерн?

Или лучше перебрать канал и определить, закрывается ли он где-то?

отправляющая часть

Должен ли я где-нибудь закрыть канал после всех итераций? но я бы наверняка закрыл его до того, как хотя бы один из гурутинов завершит работу, заканчиваясь ошибкой pani c (попытка отправить на закрытый канал)

Если бы я подключил шаблон done <- true, это будет здесь?

Группы ожидания

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

Mis c

Должен ли я передавать каналы в забавных c аргументах или позволять им быть глобальными для программы как она есть?

(плохой) код
package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"
)

const ITERATIONS = 200

var (
    results   chan string
    initial   []string
    formatted []string
)

func main() {
    defer timeTrack(time.Now(), "program")

    format()  //run format goroutines
    receive() //receive formatted strings

    log.Printf("final slice contains %d/%d elements", len(formatted), len(initial))
}

//gets all results from channel and appends them to formatted slice
func receive() {
    for i := 0; i < ITERATIONS; i++ {
        select {
        case result := <-results:
            formatted = append(formatted, result)
        }
    }
}

//loops over initial slice and runs a goroutine per element
//that does some formatting operation and then pushes result to channel
func format() {
    for i := 0; i < ITERATIONS; i++ {
        go func(i int) {
            //simulate some formatting code that can take a while
            sleep := time.Duration(rand.Intn(10)) * time.Second
            time.Sleep(sleep)
            //append formatted string to result chan
            results <- fmt.Sprintf("%s formatted", initial[i])
        }(i)
    }

}

//initialize chans and inital slice
func init() {
    results = make(chan string, ITERATIONS)
    for i := 0; i < ITERATIONS; i++ {
        initial = append(initial, fmt.Sprintf("string #%d", i))
    }
}

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s took %s", name, elapsed)
}


1 Ответ

1 голос
/ 20 апреля 2020

Я не могу найти способ иметь простую (когда-либо) l oop упаковку выбора, с двумя случаями (один получает из канала результатов, а другой - как случай <), который вернется из функции). Будет ли это лучше? </p>

Если канал закрыт после завершения работы всех авторов, вы можете использовать простой for ... range l oop:

for result := range ch {
    ... do something with the result ...
}

In Чтобы этот простой вариант работал, канал должен стать закрытым, в противном случае for l oop не прекратится.

Должен ли я где-то закрыть канал после того, как все итерации?

Если это вообще возможно, да.

я действительно не пробовал группы ожидания ...

A sync.WaitGroup или что-то очень похожее, это почти наверняка путь к go здесь. Каждая процедура, которая может записать в канал, должна учитываться изначально, например:

var wg Sync.WaitGroup
wg.Add(ITERATIONS)

Затем вы можете просто создать все свои программы, которые пишут, и позволить им работать. При каждом запуске он вызывает wg.Done(), чтобы указать, что это закончено.

Вы тогда - где-нибудь; часть , где немного сложна - позвоните wg.Wait(), чтобы дождаться завершения работы всех авторов. Когда все авторы указывают, что они сделали, вы можете close() канал.

Обратите внимание, что если вы вызываете wg.Wait() из той же самой программы, которая читает канал, т.е. процедура, которая будет запускать цикл for result := range ... - у вас проблема: вы не можете одновременно читать с канала и , ожидая, когда записывающие устройства запишут на канал. Так что вам нужно либо позвонить wg.Wait() после того, как l oop закончится, что слишком поздно; или перед запуском l oop, что слишком рано.

Это проясняет проблему и ее решение: вы должны прочитать из канала в одной процедуре и выполнить ожидание, а затем закрытие в другая программа Самое большее, одна из этих программ может быть основной, которая изначально вошла в функцию main.

Обычно довольно просто сделать процедуру ожидания и закрытия закрытой:

go func() {
    wg.Wait()
    close(results)
}()

например.

что, если по какой-то причине один из горутинов завершится неудачей?

Вам нужно будет точно определить, что вы имеете в виду на терпит неудачу здесь, но если вы имеете в виду: что, если сама вызываемая программа вызывает, скажем, panic и, следовательно, не получает на свой вызов wg.Done(), вы можете использовать defer, чтобы убедиться, что wg.Done() происходит даже в пани c:

func(args) {
    defer wg.Done()
    ... do the work ...
}

wg.Add(1)
go func(args) // `func` will definitely call `wg.Done`, even if `func` panics

Должен ли я передавать каналы в забавных c аргументах или делать их глобальными для программы как она есть?

Стилистически глобальные переменные всегда немного беспорядочные. Это не означает, что вы не можете использовать их; Вам решать, просто помните все компромиссы. Переменные замыкания не такие беспорядочные, но не забывайте соблюдать осторожность с for l oop переменными итерации:

for i := 0; i < 10; i++ {
    go func() {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(i)  // BEWARE BUG: this prints 10, not 0-9
    }()
}

ведет себя плохо. Попробуйте это на Go детской площадке ; обратите внимание, что go vet теперь жалуется на неправильное использование i здесь.

Я перенес ваш оригинальный пример кода на игровую площадку Go и внес в него минимальные изменения, как описано выше. Результат здесь . (Чтобы сделать его менее медленным, я заставил сны ждать n-сто миллисекунд вместо n секунд.)

...