channel基本語法
• channel介紹
√ golang社區口號:不要通過共享內存來通信,而應該通過通信來共享內存。
√ golang提供一種基於消息機制而非共享內存的通信模型。消息機制認為每個並發單元都是自包含的獨立個體,並且擁有自己的變量,但在不同並發單元間這些變量不共享。每個並發單元的輸入和輸出只有一種,那就是消息。
√ channel是golang在語言級提供的goroutine間的通信方式,可以使用channel在兩個或多個goroutine之間傳遞消息。
√ channel是進程內的通信方式,如果需要跨進程通信,建議使用分布式的方法來解決,比如使用Socket或HTTP等通信協議。
√ channel是類型相關的,即一個channel只能傳遞一種類型的值,需要在聲明channel時指定。可以認為channel是一種類型安全的管道。
• channel聲明語法
▶ 語法如下
var chanName chan ElementType
▶ 示例如下
var ch chan int // int類型channel var m map[string]chan bool // bool類型channel的map
• channel定義語法
▶ 語法如下
√ 定義一個channel直接使用內置的函數make()即可。
// 聲明一個channel var chanName chan ElementType // 定義一個無緩沖的channel chanName := make(chan ElementType)
// 定義一個帶緩沖的channel
chanName := make(chan ElementType, n)
• channel關閉語法
√ 關閉一個channel直接使用內置的函數close()即可。
√ 應該在生產者處關閉channel,而不是消費者處關閉channel,否則容易引起panic。
// 聲明一個channel var chanName chan ElementType // 定義一個無緩沖的channel chanName := make(chan ElementType) // 定義一個帶緩沖的channel chanName := make(chan ElementType, n)
// 關閉一個channel
close(chanName)
• channel讀寫語法
√ 向無緩沖的channel寫入數據會導致該goroutine阻塞,直到其他goroutine從這個channel中讀取數據。
√ 向帶緩沖的且緩沖已滿的channel寫入數據會導致該goroutine阻塞,直到其他goroutine從這個channel中讀取數據。
√ 向帶緩沖的且緩沖未滿的channel寫入數據不會導致該goroutine阻塞。
√ 從無緩沖的channel讀出數據,如果channel中無數據,會導致該goroutine阻塞,直到其他goroutine向這個channel中寫入數據。
√ 從帶緩沖的channel讀出數據,如果channel中無數據,會導致該goroutine阻塞,直到其他goroutine向這個channel中寫入數據。
√ 從帶緩沖的channel讀出數據,如果channel中有數據,該goroutine不會阻塞。
√ 總結:無緩沖的channel讀寫通常都會發生阻塞,帶緩沖的channel在channel滿時寫數據阻塞,在channel空時讀數據阻塞。
// 聲明一個channel var chanName chan ElementType // 定義一個無緩沖的channel chanName := make(chan ElementType)
// 定義一個帶緩沖的channel
chanName := make(chan ElementType, n) // 寫channel chanName <- value // 讀channel value := <-chanName
▶ range操作
√ golang中的range常常和channel一起使用,用來從channel中讀取所有值。
√ range操作能夠不斷讀取channel里面的數據,直到該channel被顯式的關閉。
▪ 語法如下
for value := range chanName { // ... }
▪ 示例如下
package main import "fmt" func generateString(strings chan string) { strings <- "Monday" strings <- "Tuesday" strings <- "Wednesday" strings <- "Thursday" strings <- "Friday" strings <- "Saturday" strings <- "Sunday" close(strings) } func main() { strings := make(chan string) // 無緩沖channel go generateString(strings) for s := range strings { fmt.Println(s) } }
▶ select操作
√ golang中的select關鍵字用於處理異步IO,可以與channel配合使用。
√ golang中的select的用法與switch語言非常類似,不同的是select每個case語句里必須是一個IO操作。
√ select會一直等待等到某個case語句完成才結束。
▪ 語法如下
select { case <-chan1: // 如果chan1成功讀到數據,則進行該case處理語句 case chan2 <- 1: // 如果成功向chan2寫入數據,則進行該case處理語句 default: // 如果上面都沒有成功,則進入default處理流程 }
▪ 示例如下
package main import "fmt" import "time" func main() { timeout := make(chan bool) go func() { time.Sleep(3 * time.Second) // sleep 3 seconds timeout <- true }() // 實現了對ch讀取操作的超時設置。 ch := make(chan int) select { case <-ch: case <-timeout: fmt.Println("timeout!") } }
▶ 判斷channel關閉
√ 在讀取的時候使用多重返回值來判斷一個channel是否已經被關閉。
▪ 語法如下
value, ok := <-chanName if ok { // channel未關閉 } else { // channel已關閉 }
▪ 示例如下
package main import "fmt" func generateString(strings chan string) { strings <- "Monday" strings <- "Tuesday" strings <- "Wednesday" strings <- "Thursday" strings <- "Friday" strings <- "Saturday" strings <- "Sunday" close(strings) } func main() { strings := make(chan string) // 無緩沖channel go generateString(strings) for { if s, ok := <-strings; ok { fmt.Println(s) } else { fmt.Println("channel colsed.") break } } }
• 單向channel語法
▶ 使用意義
√ golang中假如一個channel只允許讀,那么channel肯定只會是空的,因為沒機會往里面寫數據。
√ golang中假如一個channel只允許寫,那么channel最后只會是滿的,因為沒機會從里面讀數據。
√ 單向channel概念,其實只是對channel的一種使用限制,即在將一個channel變量傳遞到一個函數時,可以通過將其指定為單向channel變量,從而限制該函數中可以對此channel的操作,達到權限控制作用。
▶ 聲明語法
var ch1 chan elementType // ch1是一個正常的channel var ch2 chan<- elementType // ch2是單向channel,只用於寫數據 var ch3 <-chan elementType // ch3是單向channel,只用於讀數據
▶ 類型轉換
ch1 := make(chan elementType) ch2 := <-chan elementType(ch1) // ch2是一個單向的讀取channel ch3 := chan<- elementType(ch1) // ch3是一個單向的寫入channel
▶ 示例如下
package main import "fmt" func Parse(ch <-chan int) { for value := range ch { fmt.Println("Parsing value", value) } } func main() { var ch chan int ch = make(chan int) go func() { ch <- 1 ch <- 2 ch <- 3 close(ch) }() Parse(ch) }
channel實際運用
• 主函數等待所有goroutine完成后返回
▶ 使用意義
我們已經知道golang程序從main()函數開始執行,當main()函數返回時,程序結束且不等待其他goroutine結束。如果main函數使用time.Sleep方式阻塞等待所有goroutine返回,那么這個休眠時間勢必無法控制精確。通過使用channel可以很好解決這個問題。
▶ 使用示例
package main import "fmt" func MyRoutineFunc(ch chan int) { // 函數處理 ch <- 1 fmt.Println("MyRoutineFunc process finished.") } func main() { chs := make([]chan int, 10) for i := 0; i < 10; i++ { chs[i] = make(chan int) go MyRoutineFunc(chs[i]) } for _, ch := range chs { <-ch } fmt.Println("All goroutine finished.") }
• 實現IO超時機制
▶ 使用意義
golang沒有提供直接的超時處理機制,但我們可以利用select和channel結合來實現超時機制。
▶ 使用示例
package main import "fmt" import "time" func main() { // 實現並執行一個匿名的超時等待函數 timeout := make(chan bool, 1) go func() { time.Sleep(3 * time.Second) // 等待3秒鍾 timeout <- true }() // 然后結合使用select實現超時機制 ch := make(chan int) select { case <-ch: // 從ch中讀取到數據 case <-timeout: // 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據 fmt.Println("timeout!") } }
