5

チャネルを使用してキューを実装する演習に取り組んでいます。具体的には、チャネルのサイズを使用して同時ゴルーチンの数を制限しようとしています。つまり、次のコードを書きました。

package main

import "fmt"
import "time"
import "math/rand"

func runTask (t string, ch *chan bool) {
        start := time.Now()
        fmt.Println("starting task", t)
        time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
        fmt.Println("done running task", t, "in", time.Since(start))
        <- *ch
}

func main() {
        numWorkers := 3
        files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}

        activeWorkers := make(chan bool, numWorkers)

        for _, f := range files {
                activeWorkers <- true
                fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers))
                go runTask(f, &activeWorkers)
        }
        select{}
}

現在、コードは次のようにクラッシュします。

throw: all goroutines are asleep - deadlock!

私の期待は、select の呼び出しが永久にブロックされ、ゴルーチンがデッドロックなしで終了することでした。

したがって、2 つの質問があります。select が永久にブロックされないのはなぜですか。また、for ループの後に time.Sleep() 呼び出しをスローする以外に、どうすればデッドロックを回避できますか?

乾杯、

-mtw

4

3 に答える 3

6

Arlen Cuss はすでに良い回答を書いています。ワーク キューの別のデザインを提案したいだけです。チャネルがバッファできるエントリの数を制限する代わりに、より自然に感じられる限られた数のワーカー ゴルーチンを生成することもできます。そんな感じ:

package main

import "fmt"
import "time"
import "math/rand"

func runTask(t string) string {
    start := time.Now()
    fmt.Println("starting task", t)
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
    fmt.Println("done running task", t, "in", time.Since(start))
    return t
}

func worker(in chan string, out chan string) {
    for t := range in {
        out <- runTask(t)
    }
}

func main() {
    numWorkers := 3

    // spawn workers
    in, out := make(chan string), make(chan string)
    for i := 0; i < numWorkers; i++ {
        go worker(in, out)
    }

    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}

    // schedule tasks
    go func() {
        for _, f := range files {
            in <- f
        }
    }()

    // get results
    for _ = range files {
        <-out
    }
}

すべてのタスクが実行されるまで待ちたい場合は、 sync.WaitGroupを使用することもできますが、outチャネルを使用すると、後で結果を集計できるという利点があります。たとえば、各タスクがそのファイル内の単語数を返す場合、最後のループを使用して個々の単語数をすべて合計することができます。

于 2012-04-16T10:58:16.360 に答える
4

まず、チャネルへのポインタを渡す必要はありません。マップなどのチャネルは参照です。つまり、基になるデータはコピーされず、実際のデータへのポインタのみがコピーされます。それ自体へのポインタが必要な場合は、そのときがいつになるかがわかります。 chan

プログラムがすべてのゴルーチンがブロックされた状態になるため、クラッシュが発生します。これは不可能なはずです。すべてのゴルーチンがブロックされている場合、可能なプロセスが来て別のゴルーチンを起動することはできません(その結果、プログラムがハングします)。

主要なゴルーチンは、select {}誰かを待つのではなく、ただぶら下がっているだけで終わります。最後のrunTaskゴルーチンが終了すると、プライマリゴルーチンだけが残り、誰も待っていません。

すべてのゴルーチンがいつ終了したかを知る方法を追加する必要があります。おそらく別のチャネルが終了イベントを受信できます。

これは少し醜いですが、いくつかのインスピレーションかもしれません。

package main

import "fmt"
import "time"
import "math/rand"

func runTask(t string, ch chan bool, finishedCh chan bool) {
    start := time.Now()
    fmt.Println("starting task", t)
    time.Sleep(time.Millisecond * time.Duration(rand.Int31n(1500))) // fake processing time
    fmt.Println("done running task", t, "in", time.Since(start))
    <-ch
    finishedCh <- true
}

func main() {
    numWorkers := 3
    files := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}

    activeWorkers := make(chan bool, numWorkers)
    finishedWorkers := make(chan bool)
    done := make(chan bool)

    go func() {
        remaining := len(files)
        for remaining > 0 {
            <-finishedWorkers
            remaining -= 1
        }

        done <- true
    }()

    for _, f := range files {
        activeWorkers <- true
        fmt.Printf("activeWorkers is %d long.\n", len(activeWorkers))
        go runTask(f, activeWorkers, finishedWorkers)
    }

    <-done
}
于 2012-04-16T10:25:45.077 に答える
2

tux21b はすでにより慣用的な解決策を投稿していますが、別の方法であなたの質問に答えたいと思います。select{} は永久にブロックします。はい。すべてのゴルーチンがブロックされると、デッドロックが発生します。他のすべてのゴルーチンが終了すると、ブロックされたメインのゴルーチンだけが残り、デッドロックになります。

通常、他のすべてのゴルーチンが終了した後、その結果を使用するか、単にクリーンアップすることによって、メインのゴルーチンで何かを実行したい場合、そのために tux21b が提案したことを実行します。本当に main を終了させて​​残りのゴルーチンに仕事をさせたい場合はdefer runtime.Goexit()、main 関数の先頭に置きます。これにより、プログラムを終了せずに終了します。

于 2012-04-16T21:38:25.377 に答える