在 Python/Ruby/JavaScript/ECMAScript 6 中,可以使用语言提供的 yield 关键字编写生成器函数。在 Go 中,可以使用 goroutine 和 channel 模拟。
以下代码展示了如何实现一个排列函数(abcd、abdc、acbd、acdb、...、dcba):
// $src/lib/lib.go
package lib
// private, starts with lowercase "p"
func permutateWithChannel(channel chan<- []string, strings, prefix []string) {
length := len(strings)
if length == 0 {
// Base case
channel <- prefix
return
}
// Recursive case
newStrings := make([]string, 0, length-1)
for i, s := range strings {
// Remove strings[i] and assign the result to newStringI
// Append strings[i] to newPrefixI
// Call the recursive case
newStringsI := append(newStrings, strings[:i]...)
newStringsI = append(newStringsI, strings[i+1:]...)
newPrefixI := append(prefix, s)
permutateWithChannel(channel, newStringsI, newPrefixI)
}
}
// public, starts with uppercase "P"
func PermutateWithChannel(strings []string) chan []string {
channel := make(chan []string)
prefix := make([]string, 0, len(strings))
go func() {
permutateWithChannel(channel, strings, prefix)
close(channel)
}()
return channel
}
这是如何使用的:
// $src/main.go
package main
import (
"./lib"
"fmt"
)
var (
fruits = []string{"apple", "banana", "cherry", "durian"}
banned = "durian"
)
func main() {
channel := lib.PermutateWithChannel(fruits)
for myFruits := range channel {
fmt.Println(myFruits)
if myFruits[0] == banned {
close(channel)
//break
}
}
}
注意:
break
语句(如上所示)并不需要,因为 close(channel)
导致 range
在下一次迭代时返回 false
,循环将终止。
问题
如果调用者不需要所有排列,它需要显式地 close()
通道,否则通道将在程序终止之前不会关闭(资源泄漏发生)。另一方面,如果调用者需要所有排列(即 range
循环到末尾),调用者必须不要 close()
通道。这是因为 close()
已经关闭的通道会导致运行时恐慌(请参阅规范中此处)。然而,如果确定是否应该停止的逻辑不像上面显示的那样简单,则最好使用 defer close(channel)
。
- 如何惯用地实现像这样的生成器?
- 惯用地,谁应该负责关闭通道 - 库函数还是调用者?
- 将我的代码修改为下面这样是否是一个好主意,以便调用者无论如何都要
defer close()
通道?
在库中,修改这个:
go func() {
permutateWithChannel(channel, strings, prefix)
close(channel)
}()
转换为:
go permutateWithChannel(channel, strings, prefix)
在调用者中,修改这个:
func main() {
channel := lib.PermutateWithChannel(fruits)
for myFruits := range channel {
fmt.Println(myFruits)
if myFruits[0] == banned {
close(channel)
}
}
}
转换为:
func main() {
channel := lib.PermutateWithChannel(fruits)
defer close(channel) // <- Added
for myFruits := range channel {
fmt.Println(myFruits)
if myFruits[0] == banned {
break // <- Changed
}
}
}
4. 尽管在执行上述代码时无法观察到,且算法的正确性不受影响,在调用者使用
close()
关闭通道后,运行库代码的goroutine应该在下一次迭代中尝试向已关闭的通道发送数据时引发panic
(正如规范文档中所述),从而终止它。这会造成任何负面影响吗?5. 库函数的签名是
func(strings []string) chan []string
。理想情况下,返回类型应为<-chan []string
以将其限制为只读取。然而,如果是调用者负责close()
通道,那么它就不能被标记为“只读取”,因为close()
内置函数不适用于只读取通道。有什么惯用的处理方法?