3 回答

TA貢獻(xiàn)1840條經(jīng)驗 獲得超5個贊
一、替代方案
前言:我會使用一個更簡單的生成器,因為問題不關(guān)心生成器的復(fù)雜性,而是關(guān)心生成器和消費(fèi)者之間的信號,以及消費(fèi)者本身的調(diào)用。這個簡單的生成器只生成從0to的整數(shù)9。
1.帶函數(shù)值
通過傳遞一個簡單的消費(fèi)者函數(shù),生成消費(fèi)者模式更加清晰,它還有一個優(yōu)點(diǎn),即如果需要中止或任何其他操作,它可以返回一個值信號。
由于在示例中只有一個事件要發(fā)出信號(“中止”),消費(fèi)者函數(shù)將具有bool返回類型,在需要中止時發(fā)出信號。
所以看這個簡單的例子,消費(fèi)者函數(shù)值傳遞給生成器:
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)
}
輸出(在Go Playground上試試):
Processing 0
Processing 1
Processing 2
Processing 3
請注意,使用者 ( process) 不需要是“本地”函數(shù),它可以在 之外聲明main(),例如,它可以是全局函數(shù)或來自另一個包的函數(shù)。
該解決方案的潛在缺點(diǎn)是它僅使用 1 個 goroutine 來生成和使用值。
2. 有渠道
如果你仍然想用頻道來做,你可以。請注意,由于通道是由生成器創(chuàng)建的,并且由于消費(fèi)者對從通道接收到的值進(jìn)行循環(huán)(最好使用for ... range構(gòu)造),因此生成器有責(zé)任關(guān)閉通道。解決此問題還允許您返回僅接收通道。
是的,關(guān)閉生成器中返回的通道最好作為延遲語句完成,因此即使生成器發(fā)生恐慌,消費(fèi)者也不會被阻塞。但請注意,這個延遲關(guān)閉不是在generate()函數(shù)中,而是在匿名函數(shù)中g(shù)enerate(),作為一個新的 goroutine啟動并執(zhí)行;否則通道將在返回之前關(guān)閉generate()- 根本沒有用......
如果您想從消費(fèi)者處向生成器發(fā)出信號(例如中止而不生成更多值),您可以使用例如另一個通道,該通道傳遞給生成器。由于生成器只會“偵聽”此通道,因此它也可以聲明為生成器的僅接收通道。如果你只需要發(fā)出一個事件信號(在我們的例子中是中止),不需要在這個通道上發(fā)送任何值,一個簡單的關(guān)閉就可以了。如果您需要向多個事件發(fā)送信號,可以通過在此通道上實際發(fā)送一個值來完成,即要執(zhí)行的事件/操作(其中中止可能是多個事件中的一個)。
并且您可以使用該select語句作為處理在返回通道上發(fā)送值并觀察傳遞給生成器的通道的慣用方式。
這是一個帶有abort頻道的解決方案:
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)
}
輸出(在Go Playground上試試):
Sent 0
Processing 0
Processing 1
Sent 1
Sent 2
Processing 2
Processing 3
Sent 3
Aborting
這個解決方案的明顯優(yōu)勢是它已經(jīng)使用了 2 個 goroutines(1 個生成值,1 個消耗/處理它們),并且很容易擴(kuò)展它以使用任意數(shù)量的 goroutines 作為返回的通道來處理生成的值生成器可以同時從多個 goroutine 中使用 - 通道可以安全地同時接收,數(shù)據(jù)競爭不會發(fā)生,按照設(shè)計;更多閱讀:如果我正確使用通道,我是否需要使用互斥鎖?
二、未解決問題的答案
goroutine 上的“未捕獲”恐慌將結(jié)束 goroutine 的執(zhí)行,但不會導(dǎo)致資源泄漏問題。但是,如果作為單獨(dú)的 goroutine 執(zhí)行的函數(shù)會在非恐慌的情況下釋放由它分配的資源(在非延遲語句中),那么該代碼顯然不會運(yùn)行并且會導(dǎo)致例如資源泄漏。
您沒有觀察到這一點(diǎn),因為程序在主協(xié)程終止時終止(并且它不會等待其他非主協(xié)程完成 - 因此您的其他協(xié)程沒有機(jī)會恐慌)。請參閱規(guī)范:程序執(zhí)行。
但是要知道,panic()并且recover()是針對例外情況,它們不適用于諸如try-catchJava 中的異常和塊之類的一般用例。例如,應(yīng)該通過返回錯誤(并處理它們?。﹣肀苊饪只牛⑶铱只沤^對不應(yīng)該離開包的“邊界”(例如panic(),recover()可能有理由在包實現(xiàn)中使用,但恐慌狀態(tài)應(yīng)該被“捕獲” " 放在包裹里面,不要從里面拿出來)。

TA貢獻(xiàn)1859條經(jīng)驗 獲得超6個贊
在我看來,生成器通常只是內(nèi)部封閉的包裝器。像這樣的東西
package main
import "fmt"
// This function `generator` returns another function, which
// we define anonymously in the body of `generator`. The
// returned function _closes over_ the variable `data` to
// form a closure.
func generator(data int, permutation func(int) int, bound int) func() (int, bool) {
return func() (int, bool) {
data = permutation(data)
return data, data < bound
}
}
// permutation function
func increment(j int) int {
j += 1
return j
}
func main() {
// We call `generator`, assigning the result (a function)
// to `next`. This function value captures its
// own `data` value, which will be updated each time
// we call `next`.
next := generator(1, increment, 7)
// See the effect of the closure by calling `next`
// a few times.
fmt.Println(next())
fmt.Println(next())
fmt.Println(next())
// To confirm that the state is unique to that
// particular function, create and test a new one.
for next, generation, ok := generator(11, increment, 17), 0, true; ok; {
generation, ok = next()
fmt.Println(generation)
}
}
它看起來不像“范圍”那么優(yōu)雅,但對我來說在語義和句法上非常清晰。它有效http://play.golang.org/p/fz8xs0RYz9

TA貢獻(xiàn)1802條經(jīng)驗 獲得超10個贊
我同意icza的回答??偨Y(jié)一下,有兩種選擇:
映射函數(shù):使用回調(diào)來迭代集合。. 這樣做的缺點(diǎn)是讓控制流發(fā)揮作用。不是 Pythonic 生成器,因為它不返回可迭代序列。func myIterationFn(yieldfunc (myType)) (stopIterating bool)myGeneratormyIterationFn
通道:使用通道并警惕泄漏的 goroutine??梢赞D(zhuǎn)換myIterationFn為返回可迭代序列的函數(shù)。以下代碼提供了此類轉(zhuǎn)換的示例。
myMapper := func(yield func(int) bool) {
for i := 0; i < 5; i++ {
if done := yield(i); done {
return
}
}
}
iter, cancel := mapperToIterator(myMapper)
defer cancel() // This line is very important - it prevents goroutine leaks.
for value, ok := iter(); ok; value, ok = iter() {
fmt.Printf("value: %d\n", value)
}
這里以一個完整的程序為例。mapperToIterator做從映射函數(shù)到生成器的轉(zhuǎn)換。Go 缺乏泛型需要從interface{}to 進(jìn)行轉(zhuǎn)換int。
package main
import "fmt"
// yieldFn reports true if an iteration should continue. It is called on values
// of a collection.
type yieldFn func(interface{}) (stopIterating bool)
// mapperFn calls yieldFn for each member of a collection.
type mapperFn func(yieldFn)
// iteratorFn returns the next item in an iteration or the zero value. The
// second return value is true when iteration is complete.
type iteratorFn func() (value interface{}, done bool)
// cancelFn should be called to clean up the goroutine that would otherwise leak.
type cancelFn func()
// mapperToIterator returns an iteratorFn version of a mappingFn. The second
// return value must be called at the end of iteration, or the underlying
// goroutine will leak.
func mapperToIterator(m mapperFn) (iteratorFn, cancelFn) {
generatedValues := make(chan interface{}, 1)
stopCh := make(chan interface{}, 1)
go func() {
m(func(obj interface{}) bool {
select {
case <-stopCh:
return false
case generatedValues <- obj:
return true
}
})
close(generatedValues)
}()
iter := func() (value interface{}, notDone bool) {
value, notDone = <-generatedValues
return
}
return iter, func() {
stopCh <- nil
}
}
func main() {
myMapper := func(yield yieldFn) {
for i := 0; i < 5; i++ {
if keepGoing := yield(i); !keepGoing {
return
}
}
}
iter, cancel := mapperToIterator(myMapper)
defer cancel()
for value, notDone := iter(); notDone; value, notDone = iter() {
fmt.Printf("value: %d\n", value.(int))
}
}
- 3 回答
- 0 關(guān)注
- 1384 瀏覽
添加回答
舉報