文章目錄
- 將 channel 用作通信機制
- Channel 語法
- 無緩沖 channel
- 緩沖 channels
- channel 與 goroutine
- 緩沖 channels 示例
- 多路復用
將 channel 用作通信機制
golang學習筆記——將 channel 用作通信機制
golang學習筆記——并發計算斐波納契數
Go 中的 channel 是 goroutine 之間的通信機制。 請記住 Go 的并發方法是:不是通過共享內存通信;而是通過通信共享內存。當你需要將值從一個 goroutine 發送到另一個時,可以使用通道。 讓我們看看它們的工作原理,以及如何開始使用它們來編寫并發 Go 程序。
Channel 語法
ch <- x // sends (or writes ) x through channel ch
x = <-ch // x receives (or reads) data sent to the channel ch
<-ch // receives data, but the result is discarded
關閉 channel
close(ch)
無緩沖 channel
使用 make()
函數創建 channel 時,會創建一個無緩沖 channel,這是默認行為。 無緩沖 channel 會阻止發送操作,直到有人準備好接收數據。
package mainimport ("fmt""net/http""time"
)func main() {start := time.Now()apis := []string{"https://mp.csdn.net/","https://dev.azure.com","https://api.somewhereintheinternet.com/","https://gitcode.net/",}ch := make(chan string)for _, api := range apis {go checkAPI(api, ch)}for i := 0; i < len(apis); i++ {fmt.Print(<-ch)}elapsed := time.Since(start)fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}func checkAPI(api string, ch chan string) {_, err := http.Get(api)if err != nil {ch <- fmt.Sprintf("ERROR: %s is down!\n", api)return}ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
緩沖 channels
下面是一個理解有緩沖 channel 的簡單示例
package mainimport ("fmt"
)func send(ch chan string, message string) {ch <- message
}func main() {size := 4ch := make(chan string, size)send(ch, "one")send(ch, "two")send(ch, "three")send(ch, "four")fmt.Println("All data sent to the channel ...")for i := 0; i < size; i++ {fmt.Println(<-ch)}fmt.Println("Done!")
}
輸出
All data sent to the channel ...
one
two
three
four
Done!
試著將size改為2
重新運行程序時,將看到以下錯誤:
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.send(...)D:/golang2023/main.go:8
main.main()D:/golang2023/main.go:16 +0x97
exit status 2
channel 與 goroutine
channel 與 goroutine 有著緊密的聯系。 如果沒有另一個 goroutine 從 channel 接收數據,則整個程序可能會永久處于被阻止狀態。 正如你所見,這種情況確實會發生。
緩沖 channels 示例
使用之前用于檢查 API 的示例,并創建大小為 10 的緩沖通道
package mainimport ("fmt""net/http""time"
)func main() {start := time.Now()apis := []string{"https://management.azure.com","https://dev.azure.com","https://mp.csdn.net/","https://outlook.office.com/","https://api.somewhereintheinternet.com/","https://gitcode.net/",}ch := make(chan string, 10)for _, api := range apis {go checkAPI(api, ch)}for i := 0; i < len(apis); i++ {fmt.Print(<-ch)}elapsed := time.Since(start)fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}func checkAPI(api string, ch chan string) {_, err := http.Get(api)if err != nil {ch <- fmt.Sprintf("ERROR: %s is down!\n", api)return}ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
多路復用
最后,讓我們討論如何使用 select
關鍵字與多個通道同時交互。 有時,在使用多個 channel 時,需要等待事件發生。 例如,當程序正在處理的數據中出現異常時,可以包含一些邏輯來取消操作。
select
語句的工作方式類似于 switch
語句,但它適用于 channel。 它會阻止程序的執行,直到它收到要處理的事件。 如果它收到多個事件,則會隨機選擇一個。
select
語句的一個重要方面是,它在處理事件后完成執行。 如果要等待更多事件發生,則可能需要使用循環。
讓我們使用以下程序來看看 select
的運行情況:
package mainimport ("fmt""time"
)func process(ch chan string) {time.Sleep(3 * time.Second)ch <- "Done processing!"
}func replicate(ch chan string) {time.Sleep(1 * time.Second)ch <- "Done replicating!"
}func main() {ch1 := make(chan string)ch2 := make(chan string)go process(ch1)go replicate(ch2)for i := 0; i < 2; i++ {select {case process := <-ch1 :fmt.Println(process)case replicate := <-ch2 :fmt.Println(replicate)}}
}
輸出
Done replicating!
Done processing!
請注意,replicate
函數首先完成,這就是首先在終端中看到其輸出的原因。 main 函數存在一個循環,因為 select
語句在收到事件后立即結束,但我們仍在等待 process
函數完成。