2

私は go のチャネルと goroutines についてもっと理解しようとしているので、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)

さて、これまでに見つけたほとんどの例では、linesresultsチャネルの両方がmake(chan int, NUMBER_OF_LINES_IN_FILE). それでも、このコードを実行した後、私のプログラムは存在し、fatal error: all goroutines are asleep - deadlock!エラー メッセージが表示されます。

make(chan)基本的に私の考えでは、2 つのチャネルが必要であると考えています。1 つは、ファイルからの行を goroutine に伝達するためです (任意のサイズにすることができるため、関数呼び出しでサイズを通知する必要があるとは考えたくありません。他のチャネルはゴルーチンから結果を収集し、メイン関数でそれを使用して累積結果を計算します。

ゴルーチンとチャネルを使用してこの方法でプログラミングするための最良のオプションは何ですか? どんな助けでも大歓迎です。

4

2 に答える 2

7

@AndrewNが指摘したように、問題は各ゴルーチンがチャネルに送信しようとしているポイントに到達することですが、チャネルがバッファリングされておらず、ループまで何も読み取れないresultsため、これらの送信はブロックされます。最初にループを終了する必要があるため、そのループに到達することはありません。これは、チャネルにすべてのsを送信しようとするループです。resultsfor i := range resultsfor scanner.Scan()linelinesrange linesresults

これを修正するために最初に試みることは、何かがチャネルからすぐscanner.Scan()に読み取りを開始できるように、ものをゴルーチンに入れることです。ただし、次の問題は、ループresultsをいつ終了するかを知ることです。元のゴルーチンがチャネルからの読み取りを完了した後でのみfor i := range results、チャネルを閉じたいと考えています。チャネルを閉じた直後にチャネルを閉じることもできますが、潜在的な競合が発生する可能性があるため、チャネルを閉じる前に元の 2 つのゴルーチンが完了するのを待つのが最も安全な方法です: (プレイグラウンド リンク):resultslinesresultslinesresults

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)
}
于 2015-09-29T05:10:57.873 に答える
5

go のチャネルはデフォルトではバッファリングされていません。つまり、スポーンした匿名の goroutine は、そのチャネルから受信を試みるまで結果チャネルに送信できません。これは、 scanner.Scan()がラインチャネルをいっぱいにするまでメイン プログラムで実行を開始しません。これは、匿名関数が結果チャネルに送信してループを再開できるようになるまでブロックされます。デッドロック。

コードのもう 1 つの問題は、チャネルをバッファリングして上記を簡単に修正した場合でも、チャネルが閉じられていないため、 i := range の結果がフィードされなくなるとデッドロックすることです。

編集:バッファリングされたチャネルを避けたい場合、これが1 つの潜在的な解決策です。基本的に、最初の問題は、新しいゴルーチンを介して結果チャネルへの送信を実行し、行のループを完了させることで回避されます。2 番目の問題 (チャネルの読み取りを停止するタイミングがわからない) は、作成されたゴルーチンをカウントし、すべてのゴルーチンが考慮されたときにチャネルを明示的に閉じることによって回避されます。おそらく、待機グループで同様のことを行う方がよいでしょう。

于 2015-09-29T03:15:31.243 に答える