2016-07-09 2 views
4

Я пытаюсь решить эту проблему на Exercism:Проблемы с goroutines в цикле

Write a program that counts the frequency of letters in texts using parallel computation.

В принципе, у меня есть тип FreqMap:

type FreqMap map[rune]int 

И в Frequency функции:

func Frequency(s string) FreqMap { 
    m := make(FreqMap) 
    for _, v := range s { 
     m[v]++ 
    } 
    return m 
} 

Exercism представляет собой пример реализации параллельной версии используя рекурсию, но я хотел бы реализовать свою собственную версию, используя цикл for. Я придумал следующее решение, которое не работает:

func ConcurrentFrequency(l []string) FreqMap { 
    c := make(chan FreqMap) 
    for i := 0; i < len(l); i++ { 
     go func(i int) { 
      c <- Frequency(l[i]) 
     }(i) 
    } 
    return <- c 
} 

Это, кажется, вернуться только после 1 итерации, c, кажется, содержит результат только 1 goroutine; Я получаю тот же результат, если добавлю sync.WaitGroup.

Не могли бы вы объяснить, что мне здесь не хватает?

Заранее благодарю вас за помощь!

ответ

4

Ваш код, кажется, делает только одну итерацию, потому что ConcurrentFrequency возвращает первое значение из канала и это его. Я Quess вы хотите что-то вроде этого:

func ConcurrentFrequency(l []string) chan FreqMap { 
    c := make(chan FreqMap) 
    go func() { 
     var wg sync.WaitGroup 
     wg.Add(len(l)) 
     for _, s := range l { 
      go func(s string) { 
       defer wg.Done() 
       c <- Frequency(s) 
      }(s) 
     } 
     wg.Wait() 
     close(c) 
    }() 
    return c 
} 

Теперь возвращает канал карты, это вы probaly хотите объединить в Сигле карте:

func main() { 
    m := make(FreqMap) 
    for v := range ConcurrentFrequency([]string{"foo", "bar","zoo"}) { 
     for k, v := range v { 
      m[k] += v 
     } 
    } 
    fmt.Println(m) 
} 

более пространное объяснение, что не делает вписывается в комментарий:

В петле for _, s := range l все горуты записываются в один и тот же канал, но поскольку этот канал не является буфером ed, как только первое значение записывается в него, оно «полно», что означает, что в него не могут быть записаны никакие другие значения. Таким образом, только один горутин в цикле может завершиться, и wg.Done вызывается только один раз. Таким образом, если исходный массив имеет более одной строки, остальные гориты не могут завершиться, пока что-то не начнет потреблять значения из канала. Но в вашей версии он застрял бы в wg.Wait, поскольку не все goroutines сделаны, и поэтому ConcurrentFrequency не может вернуть канал потребителю. В том, как я написал ConcurrentFrequency, cannel может быть возвращен потребителю, и это (чтение с канала) позволяет другим вызовам записать в канал.

+0

Большое спасибо, это, похоже, действительно решает мою проблему! Не могли бы вы объяснить мне, почему нам нужен первый goroutine, который завершает цикл 'for'? Если я удалю его, произойдет «фатальная ошибка: все горуты спали - тупик!», Но я не вижу причины. Еще раз спасибо! –

+0

Не знаете, как вы изменили код, но я хочу, чтобы он зависел от wg.Wait - цикл потребления не будет достигнут тогда, поскольку код будет застрял там. Я использовал WaitGroup, чтобы канал можно было закрыть, когда все вызовы 'Frequency' возвращаются. Это обычный шаблон, когда вы возвращаете канал. – ain

+0

Итак, если закрытие goroutine первого уровня, 'wg.Wait()' тайм-ауты остальное? Мой недостаток заключается в том, что 'wg.Done()' уменьшает счетчик WaitGroup, как только закрытие goroutine в 'for'loop возвращается; то почему существует тупик, когда 'wg.Wait() 'находится на уровне' ConcurrentFrequency() ', а не в вложенном goroutine? Извините, что беспокою вас еще раз, просто пытаясь понять большую часть этого, и ваши отзывы очень ценятся! –

Смежные вопросы