Горутины, каналы и тупик

Я пытаюсь больше узнать о каналах и горутинах go, поэтому я решил создать небольшую программу, которая подсчитывает слова из файла, читаемого объектом bufio.NewScanner:

nCPUs := flag.Int("cpu", 2, "number of CPUs to use")
flag.Parse()
runtime.GOMAXPROCS(*nCPUs)    

scanner := bufio.NewScanner(file)
lines := make(chan string)
results := make(chan int)

for i := 0; i < *nCPUs; i++ {
    go func() {
        for line := range lines {
            fmt.Printf("%s\n", line)
            results <- len(strings.Split(line, " "))
        }
    }()
}

for scanner.Scan(){
    lines <- scanner.Text()
}
close(lines)


acc := 0
for i := range results {
      acc += i
 }

fmt.Printf("%d\n", acc)

Итак, в большинстве примеров, которые я нашел до сих пор, оба канала lines и results будут буферизированы, например make(chan int, NUMBER_OF_LINES_IN_FILE). Тем не менее, после запуска этого кода моя программа существует с сообщением об ошибке fatal error: all goroutines are asleep - deadlock!.

В основном я думал, что мне нужны два канала: один для передачи горутине строк из файла (поскольку он может быть любого размера, мне не нравится думать, что мне нужно сообщить размер в вызове функции make(chan). Другой канал будет собирать результаты из горутины, и в основной функции я бы использовал его, например, для вычисления накопленного результата.

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


person resilva87    schedule 28.09.2015    source источник


Ответы (2)


Как указал @AndrewN, проблема в том, что каждая горутина достигает точки, в которой она пытается отправить на канал results, но эти посылки будут заблокированы, потому что канал results не буферизован, и до цикла for i := range results из них ничего не читается. Вы никогда не попадете в этот цикл, потому что сначала вам нужно завершить цикл for scanner.Scan(), который пытается отправить все line по каналу lines, который заблокирован, потому что горутины никогда не возвращаются в range lines, потому что они застряли, отправляя на results.

Первое, что вы можете попытаться сделать, чтобы исправить это, - это поместить материал scanner.Scan() в горутину, чтобы что-то могло сразу же начать чтение с канала results. Однако следующая проблема, с которой вы столкнетесь, - это знать, когда закончить цикл for i := range results. Вы хотите, чтобы что-то закрыло канал results, но только после того, как исходные горутины завершат чтение канала lines. Вы можете закрыть канал results сразу после закрытия канала lines, однако я думаю, что это может привести к потенциальной гонке, поэтому безопаснее всего дождаться выполнения двух исходных горутин перед закрытием канала results: (ссылка на игровую площадку):

package main

import "fmt"
import "runtime"
import "bufio"
import "strings"
import "sync"

func main() {
    runtime.GOMAXPROCS(2)

    scanner := bufio.NewScanner(strings.NewReader(`
hi mom
hi dad
hi sister
goodbye`))
    lines := make(chan string)
    results := make(chan int)

    wg := sync.WaitGroup{}
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            for line := range lines {
                fmt.Printf("%s\n", line)
                results <- len(strings.Split(line, " "))
            }
            wg.Done()
        }()
    }

    go func() {
        for scanner.Scan() {
            lines <- scanner.Text()
        }
        close(lines)
        wg.Wait()
        close(results)
    }()

    acc := 0
    for i := range results {
        acc += i
    }

    fmt.Printf("%d\n", acc)
}
person Amit Kumar Gupta    schedule 29.09.2015

Каналы в go не буферизированы по умолчанию, что означает, что ни одна из созданных вами анонимных горутин не может отправлять канал результатов, пока вы не начнете попытки получать с этого канала. Это не запускается в основной программе до тех пор, пока scanner.Scan () не заполнит канал line ... что он заблокирован до тех пор, пока ваши анонимные функции не смогут отправить на канал результатов и перезапустить их циклы. Тупик.

Другая проблема в вашем коде, даже при тривиальном исправлении вышеуказанного путем буферизации каналов, заключается в том, что for i: = range results также будет блокироваться, если в него больше не будут загружаться результаты, поскольку канал не был закрыт.

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

person AndrewN    schedule 29.09.2015