Каково идиоматическое решение смущающе параллельных задач в Go? - PullRequest
0 голосов
/ 28 сентября 2018

Я сейчас смотрю на расширенную версию следующего кода:

func embarrassing(data []string) []string {
  resultChan := make(chan string)
  var waitGroup sync.WaitGroup
  for _, item := range data {
    waitGroup.Add(1)
    go func(item string) {
      defer waitGroup.Done()
      resultChan <- doWork(item)
    }(item)
  }

  go func() {
    waitGroup.Wait()
    close(resultChan)
  }()

  var results []string
  for result := range resultChan {
    results = append(results, result)
  }
  return results
}

Это просто поражает меня.Все, что это делает, может быть выражено на других языках как

results = parallelMap(data, doWork)

Даже если в Go это сделать не так легко, разве нет лучшего способа, чем описанный выше?

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018

Вы также можете использовать отражение для достижения чего-то похожего.

В этом примере она распределяет функцию-обработчик по 4 процедурам и возвращает результаты в новом экземпляре с указанным типом исходного среза.

package main

import (
    "fmt"
    "reflect"
    "strings"
    "sync"
)

func parralelMap(some interface{}, handle interface{}) interface{} {
    rSlice := reflect.ValueOf(some)
    rFn := reflect.ValueOf(handle)
    dChan := make(chan reflect.Value, 4)
    rChan := make(chan []reflect.Value, 4)
    var waitGroup sync.WaitGroup
    for i := 0; i < 4; i++ {
        waitGroup.Add(1)
        go func() {
            defer waitGroup.Done()
            for v := range dChan {
                rChan <- rFn.Call([]reflect.Value{v})
            }
        }()
    }
    nSlice := reflect.MakeSlice(rSlice.Type(), rSlice.Len(), rSlice.Cap())
    for i := 0; i < rSlice.Len(); i++ {
        dChan <- rSlice.Index(i)
    }
    close(dChan)
    go func() {
        waitGroup.Wait()
        close(rChan)
    }()
    i := 0
    for v := range rChan {
        nSlice.Index(i).Set(v[0])
        i++
    }
    return nSlice.Interface()
}

func main() {
    fmt.Println(
        parralelMap([]string{"what", "ever"}, strings.ToUpper),
    )
}

Тест здесь https://play.golang.org/p/iUPHqswx8iS

0 голосов
/ 28 сентября 2018

Если вам нужны все результаты, вам не нужен канал (и дополнительная процедура для его закрытия), чтобы сообщить о результатах, вы можете написать непосредственно в срез результатов:

func cleaner(data []string) []string {
    results := make([]string, len(data))

    wg := &sync.WaitGroup{}
    wg.Add(len(data))
    for i, item := range data {
        go func(i int, item string) {
            defer wg.Done()
            results[i] = doWork(item)
        }(i, item)
    }
    wg.Wait()

    return results
}

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

Вариант анотера: если doWork() не вернет результат, но получит адрес, где результат должен быть "помещен", и дополнительно sync.WaitGroup, чтобы сообщить о завершении, эту функцию doWork() можно выполнить «напрямую» как новую процедуру.

Мы можем создать многоразовую оболочку для doWork():

func doWork2(item string, result *string, wg *sync.WaitGroup) {
    defer wg.Done()
    *result = doWork(item)
}

Если выиметь логику обработки в таком формате, вот как она может выполняться одновременно:

func cleanest(data []string) []string {
    results := make([]string, len(data))

    wg := &sync.WaitGroup{}
    wg.Add(len(data))
    for i, item := range data {
        go doWork2(item, &results[i], wg)
    }
    wg.Wait()

    return results
}

Еще одним вариантом может быть передача канала в doWork(), по которому предполагается доставить результат.Это решение даже не требует sync.Waitgroup, поскольку мы знаем, сколько элементов мы хотим получить от канала:

func cleanest2(data []string) []string {
    ch := make(chan string)
    for _, item := range data {
        go doWork3(item, ch)
    }

    results := make([]string, len(data))
    for i := range results {
        results[i] = <-ch
    }
    return results
}

func doWork3(item string, res chan<- string) {
    res <- "done:" + item
}

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

...