34

私は現在TourofGoに取り組んでおり、特に質問66では、ゴルーチンがPythonジェネレーターと同様に使用されていると思いました。66は複雑に見えると思ったので、次のように書き直しました。

package main

import "fmt"

func fibonacci(c chan int) {
    x, y := 1, 1

    for {
        c <- x
        x, y = y, x + y
    }
}

func main() {
    c := make(chan int)
    go fibonacci(c)

    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
}

これはうまくいくようです。いくつかの質問:

  1. チャネルのバッファサイズを10とすると、fibonacciさらに10個のスポットができるだけ早くいっぱいになりmain、スポットができるだけ早く使い果たされます。これは正しいですか?これは、メモリを犠牲にして1のバッファサイズよりもパフォーマンスが高くなりますよね?
  2. チャネルは送信者によって閉じられないので、fibonacciここでスコープから外れると、メモリに関して何が起こりますか?私の期待は、一度範囲外になるcgo fibonacci、チャネルとその上のすべてのものがガベージコレクションされることです。私の腸は、これはおそらく何が起こるかではないと私に言います。
4

4 に答える 4

26

はい。バッファサイズを増やすと、コンテキストスイッチの数が減るため、プログラムの実行速度が大幅に向上する可能性があります。ゴルーチンはガベージコレクションされませんが、チャネルはガベージコレクションされます。あなたの例では、フィボナッチゴルーチンは永久に実行され(別のゴルーチンがチャネルcから読み取るのを待つ)、fib-goroutineがまだ使用しているため、チャネルcが破棄されることはありません。

これは、メモリが不足しておらず、Pythonのジェネレーターに非常に似ている、見た目が異なる別のプログラムです。

package main

import "fmt"

func fib(n int) chan int {
    c := make(chan int)
    go func() {
        x, y := 0, 1
        for i := 0; i <= n; i++ {
            c <- x
            x, y = y, x+y
        }
        close(c)
    }()
    return c
}

func main() {
    for i := range fib(10) {
        fmt.Println(i)
    }
}

または、生成するフィボナッチ数がわからない場合は、別の終了チャネルを使用して、停止するタイミングでジェネレーターgoroutineに信号を送信できるようにする必要があります。これは、golangのチュートリアルhttps://tour.golang.org/concurrency/4で説明されている内容です。

于 2012-07-08T18:38:02.160 に答える
17

@tux21bの答えが好きです。関数でチャネルを作成fib()すると、呼び出し元のコードがきれいになります。少し詳しく説明すると、関数を呼び出したときにいつ停止するかを関数に指示する方法がない場合にのみ、別個の「終了」チャネルが必要です。「Xまでの数」だけを気にする場合は、次のようにすることができます。

package main

import "fmt"

func fib(n int) chan int {
    c := make(chan int)

    go func() {
        x, y := 0, 1

        for x < n {
            c <- x
            x, y = y, x+y
        }

        close(c)
    }()

    return c
}

func main() {
    // Print the Fibonacci numbers less than 500
    for i := range fib(500) {
        fmt.Println(i)
    }
}

どちらかを実行できるようにしたい場合、これは少しずさんですが、個人的には、呼び出し元で状態をテストしてから、別のチャネルを介して終了を通知するよりも優れています。

func fib(wanted func (int, int) bool) chan int {
    c := make(chan int)

    go func() {
        x, y := 0, 1

        for i := 0; wanted(i, x); i++{
            c <- x
            x, y = y, x+y
        }

        close(c)
    }()

    return c
}

func main() {
    // Print the first 10 Fibonacci numbers
    for n := range fib(func(i, x int) bool { return i < 10 }) {
        fmt.Println(n)
    }

    // Print the Fibonacci numbers less than 500
    for n := range fib(func(i, x int) bool { return x < 500 }) {
        fmt.Println(n)
    }
}

私はそれがあなたがするかどうかは与えられた状況の詳細に依存すると思います:

  1. によって作成するときに停止するタイミングをジェネレータに指示します
    1. 生成する値の明示的な数を渡す
    2. 目標値を渡す
    3. 続行するかどうかを決定する関数を渡す
  2. ジェネレータに「終了」チャネルを与え、値を自分でテストし、必要に応じて終了するように指示します。

まとめて実際に質問に答えるには:

  1. チャネルサイズを大きくすると、コンテキストスイッチが少なくなるため、パフォーマンスが向上します。この些細な例では、パフォーマンスもメモリ消費も問題になりませんが、他の状況では、チャネルのバッファリングが非常に良いアイデアであることがよくあります。ほとんどの場合、によって使用されるメモリはmake (chan int, 100)ほとんど重要ではないように見えますが、パフォーマンスに大きな違いをもたらす可能性があります。

  2. 関数には無限ループがあるfibonacciため、関数を実行しているゴルーチンは永久に実行されます(c <- xこの場合はブロックオン)。(発信者のスコープから外れると)共有しているチャネルから二度と読み取らないという事実はc、それを変更しません。また、@ tux21bが指摘しているように、チャネルはまだ使用されているため、ガベージコレクションされることはありません。これは、チャネルを閉じること(チャネルの受信側に、これ以上値が来ないことを知らせることを目的としています)とは関係がなく、関数から戻らないことと関係があります。

于 2012-07-10T22:22:13.823 に答える
13

クロージャを使用してジェネレータをシミュレートできます。これがgolang.orgの例です。

package main

import "fmt"

// fib returns a function that returns
// successive Fibonacci numbers.
func fib() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    f := fib()
    // Function calls are evaluated left-to-right.
    fmt.Println(f(), f(), f(), f(), f())
}
于 2013-05-30T14:37:05.893 に答える
8

チャネルを使用してPythonジェネレーターをエミュレートするのは一種の作業ですが、チャネルを使用すると、必要のない場合に同時実行性が導入され、おそらく必要とされるよりも複雑になります。ここでは、状態を明示的に保持するだけで、理解しやすく、短くなり、ほぼ確実により効率的になります。バッファサイズとガベージコレクションに関するすべての質問が無駄になります。

type fibState struct {
    x, y int
}

func (f *fibState) Pop() int {
    result := f.x
    f.x, f.y = f.y, f.x + f.y
    return result
}

func main() {
    fs := &fibState{1, 1}
    for i := 0; i < 10; i++ {
        fmt.Println(fs.Pop())
    }
}
于 2012-07-29T08:46:04.027 に答える