使用Go語言簡單模擬Python的生成器
def demo_input_and_output(): input = yield 'what is the input?' yield 'input is: %s' % input gen = demo_input_and_output() print(gen.next()) print(gen.send(42))
這段代碼演示了 python generator 的功能??梢钥吹?yield 同時做了兩個操作,一個是往外發(fā)數(shù)據(jù) "waht is the input",同時做的操作是往里收數(shù)據(jù) input。而且這個接收數(shù)據(jù)的操作是一個阻塞的操作,如果外部沒有調(diào)用 next() (也就是往里傳遞None),或者調(diào)用send(42)(也就是往里傳遞42這個值),那么這個阻塞的操作就會一直等待下去。
也就是說 python 的 generator 自帶了一個對外通信的 channel,用于收發(fā)消息。用 go 模擬 python 的 generator 的話寫起來就是這樣的
import "fmt"
func demoInputAndOutput(channel chan string) {
channel <- "what is my input?"
input := <- channel
channel <- fmt.Sprintf("input is: %s", input)
}
func main() {
channel := make(chan string)
go demoInputAndOutput(channel)
fmt.Println(<- channel)
channel <- "42"
fmt.Println(<- channel)
}
這段代碼和 python 版本基本上等價。隱含的 channel 在 go 版本里變成顯式的了。yield 變成了 channel <- 操作,同時立馬做了一個 <- channel 的阻塞讀操作。這也就是 yield 的本質(zhì)吧。
go 的 channel 也可以當(dāng)成 iterator 被 for 循環(huán)使用:
import "fmt"
func someGenerator() <-chan string {
channel := make(chan string)
go func() {
channel <- "a"
fmt.Println("after a")
channel <- "c"
fmt.Println("after c")
channel <- "b"
fmt.Println("after b")
close(channel)
}()
return channel
}
func main() {
channel := someGenerator()
for val := range channel {
fmt.Println(val)
}
}
和 python 的 yield 不同,這里的 channel <- 不等價于 yield,它會往下執(zhí)行直到阻塞。效果是
a
c
after c
after b
b
這和預(yù)期的順序不一樣。這里沒有把 after a after c after b 都打印出來是因為 channel 默認(rèn)只有一個元素的buffer,所以寫入了一個就阻塞了。如果增大 buffer,那么就有效果了
輸出變成了:
after a after c after b a c b
可見 goroutine 就好象一個獨立的線程一樣自己和自己玩去了,不用等待被執(zhí)行。如果要模擬 yield 就要加上顯示的同步操作(從 channel 里阻塞讀取信號):
import "fmt"
func someGenerator() chan string {
channel := make(chan string)
go func() {
channel <- "a"
<- channel
fmt.Println("after a")
channel <- "c"
<- channel
fmt.Println("after c")
channel <- "b"
<- channel
fmt.Println("after b")
close(channel)
}()
return channel
}
func main() {
channel := someGenerator()
for val := range channel {
fmt.Println(val)
channel <- ""
}
}
輸出的結(jié)果就是
a after a c after c b after b
到這里我們可以看到,python 的 generator 就好象是 golang 的 goroutine 帶了一個無buffer的channel。這樣導(dǎo)致每次yield一個值,都會產(chǎn)生一次協(xié)程上下文切換。雖然協(xié)程上下文切換很廉價,但是也不是沒有成本。像 goroutine 的 buffered channel 這樣的設(shè)計,可以讓一個 goroutine 一次性多產(chǎn)生一些輸出再阻塞等待,而不是產(chǎn)生一個輸出就阻塞等待一下,再產(chǎn)生另外一個輸出。golang rocks!
相關(guān)文章
Go常用標(biāo)準(zhǔn)庫之fmt的簡介與使用詳解
fmt 是 Go 語言中的一個常用標(biāo)準(zhǔn)庫,它用于格式化輸入和輸出數(shù)據(jù),這篇文章主要為大家介紹了fmt的基本使用,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10
Golang Printf,Sprintf,Fprintf 格式化詳解
這篇文章主要介紹了Golang Printf,Sprintf,Fprintf 格式化詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
如何控制Go編碼JSON數(shù)據(jù)時的行為(問題及解決方案)
今天來聊一下我在Go中對數(shù)據(jù)進(jìn)行 JSON 編碼時遇到次數(shù)最多的三個問題以及解決方法,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友參考下吧2020-02-02

