golang中channel的用法

極速蝸蝸發表於2020-11-09

如果說goroutine是Go語言程式的併發體的話,那麼channels則是它們之間的通訊機制。一個
channel是一個通訊機制,它可以讓一個goroutine通過它給另一個goroutine傳送值資訊。

1 建立channel

每個channel都有一個特殊的型別,也就是channels可傳送資料的型別。一個可以傳送int型別資料
的channel一般寫為chan int。使用內建的make函式,如果第二個引數大於0,則表示建立一個帶快取的channel。

ch := make(chan int) // ch has type 'chan int'
ch = make(chan int, 3) // buffered channel with capacity 3

2 channel的傳送和接受

一個傳送語句將一個值從一個goroutine通過channel傳送到另一個執行接收操作的goroutine。傳送和接收兩個操作都使用<-運算子。在傳送語句中,<-運算子分割channel和要傳送的值。在接收語句中,<-運算子寫在channel物件之前。一個不使用接收結果的接收操作也是合法的。

ch <- x
// a send statement
x = <-ch // a receive expression in an assignment statement
<-ch
// a receive statement; result is discarded

3 channel的close

Channel還支援close操作,用於關閉channel,隨後對基於該channel的任何傳送操作都將導致panic異常。對一個已經被close過的channel進行接收操作依然可以接受到之前已經成功傳送的資料,如果channel中已經沒有資料的話將產生一個零值的資料。使用內建的close函式就可以關閉一個channel:

close(ch)

4 不帶快取的Channels

一個基於無快取Channels的傳送操作將導致傳送者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當傳送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發生,那麼接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行傳送操作。
基於無快取Channels的傳送和接收操作將導致兩個goroutine做一次同步操作。因為這個原因,無快取Channels有時候也被稱為同步Channels。

5 串聯的Channels

Channels也可以用於將多個goroutine連線在一起,一個Channel的輸出作為下一個Channel的輸入。這種串聯的Channels就是所謂的管道(pipeline)。

func main() {
	naturals := make(chan int)
	squares := make(chan int)
	// Counter
	go func() {
		for x := 0; x < 100; x++ {
			naturals <- x
		}
		close(naturals)
	}()
	// Squarer
	go func() {
		for x := range naturals {
			squares <- x * x
		}
		close(squares)
	}()
	// Printer (in main goroutine)
	for x := range squares {
		fmt.Println(x)
	}
}

當一個被關閉的channel中已經傳送的資料都被成功接收後,後續的接收操作將不再阻塞,它們會立即返回一個零值。
Go語言的range迴圈可直接在channels上面迭代。使用range迴圈依次從channel接收資料,當channel被關閉並且沒有值可接收時跳出迴圈。

6 單方向的Channels

為了防止被濫用,Go語言的型別系統提供了單方向的channel型別,分別用於只傳送或只接收的channel。型別<-chan int表示一個只接收int的channel, chan<- int表示一個只傳送int的channel,(箭頭<-和關鍵字chan的相對位置表明了channel的方向。),這種限制將在編譯期檢測。

func counter(out chan<- int) {
	for x := 0; x < 100; x++ {
			out <- x
		}
		close(out)
	}
	func squarer(out chan<- int, in <-chan int) {
		for v := range in {
			out <- v * v
		}
		close(out)
	}
	func printer(in <-chan int) {
		for v := range in {
			fmt.Println(v)
		}
	}
	func main() {
		naturals := make(chan int)
		squares := make(chan int)
		go counter(naturals)
		go squarer(squares, naturals)
		printer(squares)
}

7 帶快取的Channels

帶快取的Channel內部持有一個元素佇列。佇列的最大容量是在呼叫make函式建立channel時通過第二個引數指定的。
向快取Channel的傳送操作就是向內部快取佇列的尾部插入元素,接收操作則是從佇列的頭部刪除元素。如果內部快取佇列是滿的,那麼傳送操作將阻塞直到因另一個goroutine執行接收操作而釋放了新的佇列空間。相反,如果channel是空的,接收操作將阻塞直到有另一個goroutine執行傳送操作而向佇列插入元素。

相關文章