3 回答

TA貢獻1836條經(jīng)驗 獲得超13個贊
這是一個相當(dāng)冗長的答案,但簡單地說:
過去
time.Sleep
一直等到希望其他例程完成他們的工作是不好的。除了他們通過渠道交換的類型之外,消費者和生產(chǎn)者不應(yīng)該知道彼此的任何信息。您的代碼依賴于消費者和生產(chǎn)者都知道將傳遞多少整數(shù)。不是一個現(xiàn)實的場景
通道可以迭代(將它們視為線程安全的共享切片)
通道應(yīng)該關(guān)閉
在這個相當(dāng)冗長的答案的底部,我試圖解釋一些基本概念和最佳實踐(好吧,更好的實踐),你會發(fā)現(xiàn)你的代碼被重寫以工作并顯示所有值而不依賴于time.Sleep
. 我沒有測試該代碼,但應(yīng)該沒問題
是的,這里有幾個問題。就像一個項目符號列表:
你的通道被緩沖到 1,這很好,但沒有必要
你的頻道從未關(guān)閉
您正在等待 500ns,然后退出,無論例程是否已完成,甚至是否已開始處理該問題。
沒有對例程的集中控制,一旦你啟動它們,你就沒有控制權(quán)。如果您按下 ctrl+c,您可能希望在編寫處理重要數(shù)據(jù)的代碼時取消例程。檢查信號處理和上下文
通道緩沖器
鑒于您已經(jīng)知道要將多少價值推送到您的頻道,為什么不簡單地創(chuàng)造呢ch := make(chan int, 100)
?這樣,無論消費者做什么,您的發(fā)布者都可以繼續(xù)將消息推送到頻道。
您不需要這樣做,但是根據(jù)您要執(zhí)行的操作向您的頻道添加一個合理的緩沖區(qū)絕對值得一試。不過目前,這兩個例程都在使用fmt.Println
& co,這將成為任何一種方式的瓶頸。打印到 STDOUT 是線程安全的,并且是緩沖的。這意味著每次調(diào)用fmt.Print*
都會獲得一個鎖,以避免來自兩個例程的文本被合并。
關(guān)閉通道
您可以簡單地將所有值推送到您的頻道,然后關(guān)閉它。然而,這是一種糟糕的形式。WRT 通道的經(jīng)驗法則是通道是在同一個例程中創(chuàng)建和關(guān)閉的。意思是:你正在主例程中創(chuàng)建通道,那是它應(yīng)該關(guān)閉的地方。
您需要一種機制來同步,或者至少密切關(guān)注您的例程是否已完成其工作。這是使用sync
包或通過第二個渠道完成的。
// using a done channel
func produce(ch chan<- int) <-chan struct{} {
done := make(chan struct{})
go func() {
for i := 0; i < 100; i++ {
ch <- i
}
// all values have been published
// close done channel
close(done)
}()
return done
}
func main() {
ch := make(chan int, 1)
done := produce(ch)
go consume(ch)
<-done // if producer has done its thing
close(ch) // we can close the channel
}
func consume(ch <-chan int) {
// we can now simply loop over the channel until it's closed
for i := range ch {
fmt.Printf("Consumed %d\n", i)
}
}
好的,但是在這里您仍然需要等待consume例程完成。
您可能已經(jīng)注意到,done通道在技術(shù)上并沒有在創(chuàng)建它的同一個例程中關(guān)閉。但是,因為例程被定義為閉包,所以這是一個可以接受的折衷方案?,F(xiàn)在讓我們看看如何使用等待組:
import (
"fmt"
"sync"
)
func product(wg *sync.WaitGroup, ch chan<- int) {
defer wg.Done() // signal we've done our job
for i := 0; i < 100; i++ {
ch <- i
}
}
func main() {
ch := make(chan int, 1)
wg := sync.WaitGroup{}
wg.Add(1) // I'm adding a routine to the channel
go produce(&wg, ch)
wg.Wait() // will return once `produce` has finished
close(ch)
}
好的,這看起來很有希望,我可以讓例程告訴我它們何時完成任務(wù)。但是,如果我將消費者和生產(chǎn)者都添加到等待組,我就不能簡單地遍歷通道。只有當(dāng)兩個例程都調(diào)用時,通道才會關(guān)閉wg.Done(),但如果消費者卡在一個永遠不會關(guān)閉的通道上循環(huán),那么我就創(chuàng)建了一個死鎖。
解決方案:
此時混合將是最簡單的解決方案:將消費者添加到等待組,并使用生產(chǎn)者中的完成通道來獲取:
func produce(ch chan<- int) <-chan struct{} {
done := make(chan struct{})
go func() {
for i := 0; i < 100; i++ {
ch <- i
}
close(done)
}()
return done
}
func consume(wg *sync.WaitGroup, ch <-chan int) {
defer wg.Done()
for i := range ch {
fmt.Printf("Consumer: %d\n", i)
}
}
func main() {
ch := make(chan int, 1)
wg := sync.WaitGroup{}
done := produce(ch)
wg.Add(1)
go consume(&wg, ch)
<- done // produce done
close(ch)
wg.Wait()
// consumer done
fmt.Println("All done, exit")
}

TA貢獻1794條經(jīng)驗 獲得超7個贊
我稍微改變了(延長時間。睡眠)你的代碼。在我的 Linux x86_64 上運行良好
func Product(ch chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("Product:", i)
ch <- i
}
}
func Consumer(ch <-chan int) {
for i := 0; i < 10; i++ {
a := <-ch
fmt.Println("Consmuer:", a)
}
}
func main() {
ch := make(chan int, 1)
go Product(ch)
go Consumer(ch)
time.Sleep(10000)
}
輸出 go run s1.go
Product: 0
Product: 1
Product: 2

TA貢獻1712條經(jīng)驗 獲得超3個贊
time.Sleep
需要一個time.Duration
,而不是一個整數(shù)。godoc顯示了如何正確調(diào)用它的示例。在你的情況下,你可能想要:
time.Sleep(500?*?time.Millisecond)
您的程序快速退出(但沒有給您錯誤)的原因是由于(有點令人驚訝)的time.Duration
實施方式。
time.Duration
只是 的類型別名int64
。在內(nèi)部,它使用該值來表示以納秒為單位的持續(xù)時間。當(dāng)您調(diào)用 時time.Sleep(500)
,編譯器會很樂意將數(shù)字文字解釋500
為time.Duration
.?不幸的是,這意味著 500 ns。
time.Millisecond
是一個常數(shù),等于毫秒中的納秒數(shù) (1,000,000)。好處是,要求您顯式地進行乘法運算會使調(diào)用者清楚地知道該參數(shù)的單位是什么。不幸的是,time.Sleep(500)
這是完全有效的 go 代碼,但沒有達到大多數(shù)初學(xué)者的預(yù)期。
- 3 回答
- 0 關(guān)注
- 160 瀏覽
添加回答
舉報