Golang 語言中 Channel 的使用方式
介紹
在「Effective Go」併發章節講到,“不要通過共享內存進行通信;而是通過通信共享內存”。由此表明 Golang 語言官方鼓勵用戶使用 “通過通信共享內存” 的方式併發編程。
但是,Golang 語言也在標準庫 sync 包中提供了傳統的同步原語。
我們應該選擇哪種併發編程方式呢?Golang 語言官方也給了使用指南:
- Channel passing ownership of data, distributing units of work, communicating async results
- Mutex caches, state
如上所示,傳遞數據的所有權,分發工作任務和通信異步結果,這三種場景建議使用 Channel。緩存和狀態,這兩種場景建議使用 Mutex。
在 Golang 語言官方博客「The Go Memory Model」一文中,對 channel 的描述如下:
A send on a channel happens before the corresponding receive from that channel completes.
A receive from an unbuffered channel happens before the send on that channel completes.
本文我們主要介紹一些關於 Channel 的使用方式。關於 Golang 語言 sync 包的同步原語和 Channel 的介紹,我們在之前的文章已經介紹過了,在此不再贅述,感興趣的讀者朋友可以按需翻閱。
無緩衝 channel
無緩衝 channel 可用於兩個 goroutine 之間傳遞信號,比如以下示例:
順序打印 1 至 100 的奇數和偶數:
func main () {
block := make(chan struct{})
go odd(block)
go even(block)
time.Sleep(time.Second)
fmt.Println("done")
}
func odd (block chan struct{}) {
for i := 1; i <= 100; i++ {
<-block
if i % 2 == 1 {
fmt.Println("奇數:", i)
}
}
}
func even (block chan struct{}) {
for i := 1; i <= 100; i++ {
block <- struct{}{}
if i % 2 == 0 {
fmt.Println("偶數:", i)
}
}
}
閱讀上面這段代碼,我們使用一個無緩衝 channel 作爲兩個 goroutine 之間的信號傳遞的橋樑。
主子 goroutine 之間傳遞信號:
func main () {
block := make(chan struct{})
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// block <- struct{}{}
close(block)
}()
<-block
fmt.Println("done")
}
閱讀上面這段代碼,我們使用一個無緩衝 channel 作爲主子 goroutine 之間的信號傳遞的橋樑。通過信號傳遞,主 goroutine 在子 goroutine 運行結束之後再退出。
有緩衝 channel
有緩衝 channel 可以用於解耦操作,模擬消息隊列。“生產者”和 “消費者” 只需各自處理 channel,實現解耦。
解耦 “生產者” 和“消費者”:
func main () {
task := make(chan int, 10)
go consumer(task)
// 生產者
for i := 0; i < 10; i++ {
task <- i
}
time.Sleep(time.Second * 2)
}
// 消費者
func consumer (task <-chan int) {
for i := 0; i < 10; i++ {
go func(id int) {
t := <- task
fmt.Println(id, t)
}(i)
}
}
閱讀上面這段代碼,我們使用一個有緩衝的 channel,將 “生產者” 和“消費者”做解耦操作。
超時操作和定時器
我們還可以通過 select 和 channel,實現超時操作和定時器。
超時操作:
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(1 * time.Second):
fmt.Println("timeout 1")
}
c2 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
}
閱讀上面這段代碼,我們通過 c1 和 c2 兩個 channel,分別模擬出超時和未超時場景。
定時器:
func main() {
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
done <- true
fmt.Println("Ticker stopped")
}
閱讀上面這段代碼,我們定義一個打點器,每間隔 500ms 執行一次操作,當打點器 stop 時,通過一個無緩衝 channel 傳遞退出信號。
總結
本文我們介紹了一些關於 Channel 的使用方式,我們在閱讀完本文後可以瞭解無緩衝 channel 作爲信號傳遞的使用方式和有緩衝 channel 解耦操作的方式,以及 channel 與 select 配合使用的用法。
參考資料:
https://golang.org/doc/effective_go#concurrency
https://github.com/golang/go/wiki/MutexOrChannel
https://tip.golang.org/ref/mem#tmp_3
https://gobyexample.com/timeouts
https://gobyexample.com/tickers
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vXt0gqnxPK1qbChzZxV94A