Recursion 在Golang中实现递归函数生成器(yield)的惯用方法

Recursion 在Golang中实现递归函数生成器(yield)的惯用方法,recursion,go,generator,yield,Recursion,Go,Generator,Yield,[注:我读过,这不是它的副本。] 在Python/Ruby/JavaScript/ECMAScript 6中,可以使用该语言提供的yield关键字编写生成器函数。在Go中,可以使用goroutine和通道来模拟 代码 以下代码显示了如何实现置换函数(abcd、abdc、acbd、acdb、…、dcba): /$src/lib/lib.go 包库 //private,以小写字母“p”开头 func permutateWithChannel(信道信道替代方案 前言:我将使用一个简单得多的生成器,因为

[注:我读过,这不是它的副本。]

在Python/Ruby/JavaScript/ECMAScript 6中,可以使用该语言提供的
yield
关键字编写生成器函数。在Go中,可以使用goroutine和通道来模拟

代码 以下代码显示了如何实现置换函数(abcd、abdc、acbd、acdb、…、dcba):

/$src/lib/lib.go
包库
//private,以小写字母“p”开头
func permutateWithChannel(信道信道替代方案
前言:我将使用一个简单得多的生成器,因为问题不在于生成器的复杂性,而在于生成器和使用者之间的信号以及使用者本身的调用。这个简单的生成器只生成从
0
9
的整数

1.具有函数值 generate consumer模式更简洁,只传递了一个简单的consumer函数,它还有一个优点,即如果需要终止或任何其他操作,它可以返回一个值

并且,由于在该示例中,只有一个事件需要发送信号(“中止”),因此消费者功能将具有
bool
返回类型,如果需要中止,则发送信号

因此,请参见此简单示例,其中消费者函数值传递给生成器:

func generate(process func(x int) bool) {
    for i := 0; i < 10; i++ {
        if process(i) {
            break
        }
    }
}

func main() {
    process := func(x int) bool {
        fmt.Println("Processing", x)
        return x == 3 // Terminate if x == 3
    }
    generate(process)
}
请注意,使用者(
process
)不需要是“本地”函数,它可以在
main()
之外声明,例如,它可以是全局函数或来自另一个包的函数

此解决方案的潜在缺点是,它仅使用1个goroutine来生成和使用值

2.有渠道 如果您仍然想对通道执行此操作,则可以。请注意,由于通道是由生成器创建的,并且由于消费者循环从通道接收到的值(理想情况下使用
for…range
构造),生成器负责关闭通道。解决此问题还允许您返回仅接收通道

是的,关闭生成器中返回的通道最好作为延迟语句完成,因此即使生成器崩溃,使用者也不会被阻止。但是请注意,此延迟关闭不是在
generate()
函数中,而是在从
generate()开始的匿名函数中
并作为新的goroutine执行;否则通道将在从
generate()
返回之前关闭-根本没有用处

如果您想从使用者向生成器发送信号(例如中止并不生成进一步的值),您可以使用另一个通道,该通道被传递给生成器。由于生成器将仅“侦听”该通道,因此也可以将其声明为发生器的仅接收通道。如果您只需要向一个事件发送信号(在本例中为中止),无需在此通道上发送任何值,只需简单的关闭即可。如果需要向多个事件发送信号,则可以通过实际发送此通道上的值来完成,即要执行的事件/操作(其中中止可能是多个事件中的一个)

您可以使用作为惯用方法来处理在返回的通道上发送值并查看传递给生成器的通道

下面是一个带有
中止
通道的解决方案:

func generate(abort <-chan struct{}) <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch)
        for i := 0; i < 10; i++ {
            select {
            case ch <- i:
                fmt.Println("Sent", i)
            case <-abort: // receive on closed channel can proceed immediately
                fmt.Println("Aborting")
                return
            }
        }
    }()
    return ch
}

func main() {
    abort := make(chan struct{})
    ch := generate(abort)
    for v := range ch {
        fmt.Println("Processing", v)
        if v == 3 { // Terminate if v == 3
            close(abort)
            break
        }
    }
    // Sleep to prevent termination so we see if other goroutine panics
    time.Sleep(time.Second)
}
此解决方案的明显优势在于它已经使用了2个goroutine(1个生成值,1个消费/处理值),并且很容易扩展它来处理生成的具有任意数量的goroutine的值,因为生成器返回的通道可以同时从多个goroutine使用-通道可以安全地同时从多个goroutine接收数据,根据设计,数据争用不会发生;为了获得更多的读取:

二、 未解决问题的答案 goroutine上的“未捕获”恐慌将结束goroutine的执行,但不会导致资源泄漏问题。但是如果作为单独goroutine执行的函数将释放资源(在非延迟语句中)在非紧急情况下由它分配,该代码显然不会运行,例如会导致资源泄漏

您没有注意到这一点,因为程序在主goroutine终止时终止(并且它不会等待其他非主goroutine完成-因此您的其他goroutine没有机会恐慌)。请参阅


但是要知道,
panic()
recover()
用于异常情况,它们不适用于Java中的异常和
try-catch
块这样的一般用例。应该通过返回错误(并处理它们!)来避免恐慌,并且panic绝对不应该离开包的“边界”(例如,
panic()
recover()
可以在包实现中使用,但是恐慌状态应该被“捕获”在包内,而不是释放出来)。

在我看来,通常生成器只是内部闭包的包装器。类似这样的东西

主程序包
输入“fmt”
//此函数“generator”返回另一个函数,该函数
//我们在“生成器”的主体中匿名定义
//返回函数u将变量'data'关闭到
//结案。
func生成器(数据int,置换func(int)int,绑定int)func()(int,bool){
return func()(int,bool){
数据=排列(数据)
返回数据,数据<绑定
}
}
//置换函数
函数增量(j int)int{
j+=1
返回j
}
func main(){
//我们调用“生成器”,分配结果(一个函数)
//到“下一步”。此函数值捕获其
//拥有“数据”值,该值将每次更新
//我们叫‘下一个’。
下一步:=生成器(1,增量,7)
//通过调用'next'查看闭包的效果`
//几次。
fmt.Println(next())
fmt.Println(next())
func generate(abort <-chan struct{}) <-chan int {
    ch := make(chan int)
    go func() {
        defer close(ch)
        for i := 0; i < 10; i++ {
            select {
            case ch <- i:
                fmt.Println("Sent", i)
            case <-abort: // receive on closed channel can proceed immediately
                fmt.Println("Aborting")
                return
            }
        }
    }()
    return ch
}

func main() {
    abort := make(chan struct{})
    ch := generate(abort)
    for v := range ch {
        fmt.Println("Processing", v)
        if v == 3 { // Terminate if v == 3
            close(abort)
            break
        }
    }
    // Sleep to prevent termination so we see if other goroutine panics
    time.Sleep(time.Second)
}
Sent 0
Processing 0
Processing 1
Sent 1
Sent 2
Processing 2
Processing 3
Sent 3
Aborting