go 學習筆記---chan

若是說 goroutine 是 Go語言程序的併發體的話,那麼 channels 就是它們之間的通訊機制。一個 channels 是一個通訊機制,它可讓一個 goroutine 經過它給另外一個 goroutine 發送值信息。每一個 channel 都有一個特殊的類型,也就是 channels 可發送數據的類型。一個能夠發送 int 類型數據的 channel 通常寫爲 chan int。golang

Go語言提倡使用通訊的方法代替共享內存,當一個資源須要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,並提供了確保同步交換數據的機制。聲明通道時,須要指定將要被共享的數據的類型。能夠經過通道共享內置類型、命名類型、結構類型和引用類型的值或者指針。

這裏通訊的方法就是使用通道(channel),以下圖所示。併發

 

 

 

通道的特性

Go語言中的通道(channel)是一種特殊的類型。在任什麼時候候,同時只能有一個 goroutine 訪問通道進行發送和獲取數據。goroutine 間經過通道就能夠通訊。

通道像一個傳送帶或者隊列,老是遵循先入先出(First In First Out)的規則,保證收發數據的順序。函數

聲明通道類型

通道自己須要一個類型進行修飾,就像切片類型須要標識元素類型。通道的元素類型就是在其內部傳輸的數據類型,聲明以下:ui

var 通道變量 chan 通道類型.net

  • 通道類型:通道內的數據類型。
  • 通道變量:保存通道的變量。


chan 類型的空值是 nil,聲明後須要配合 make 後才能使用。指針

建立通道

通道是引用類型,須要使用 make 進行建立,格式以下:code

通道實例 := make(chan 數據類型)blog

  • 數據類型:通道內傳輸的元素類型。
  • 通道實例:經過make建立的通道句柄。


請看下面的例子:接口

ch1 := make(chan int) // 建立一個整型類型的通道
ch2 := make(chan interface{}) // 建立一個空接口類型的通道, 能夠存聽任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip) // 建立Equip指針類型的通道, 能夠存放*Equip

 

使用通道發送數據

通道建立後,就可使用通道進行發送和接收操做。隊列

1) 通道發送數據的格式

通道的發送使用特殊的操做符<-,將數據經過通道發送的格式爲:

通道變量 <- 值
  • 通道變量:經過make建立好的通道實例。
  • 值:能夠是變量、常量、表達式或者函數返回值等。值的類型必須與ch通道的元素類型一致。

2) 經過通道發送數據的例子

使用 make 建立一個通道後,就可使用<-向通道發送數據,代碼以下:

// 建立一個空接口通道
ch := make(chan interface{})
// 將0放入通道中
ch <- 0
// 將hello字符串放入通道中
ch <- "hello"

 

3) 發送將持續阻塞直到數據被接收

把數據往通道中發送時,若是接收方一直都沒有接收,那麼發送操做將持續阻塞。Go 程序運行時能智能地發現一些永遠沒法發送成功的語句並作出提示,代碼以下: 

 

package main
func main() {
    // 建立一個整型通道
    ch := make(chan int)
    // 嘗試將0經過通道發送
    ch <- 0
}

  運行結果:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	D:/example.v2/src/study/main.go:7 +0x57
Error: process exited with code 2.

 報錯的意思是:運行時發現全部的 goroutine(包括main)都處於等待 goroutine。也就是說全部 goroutine 中的 channel 並無造成發送和接收對應的代碼。

使用通道接收數據

通道接收一樣使用<-操做符,通道接收有以下特性:
① 通道的收發操做在不一樣的兩個 goroutine 間進行。

因爲通道的數據在沒有接收方處理時,數據發送方會持續阻塞,所以通道的接收一定在另一個 goroutine 中進行。

② 接收將持續阻塞直到發送方發送數據。

若是接收方接收時,通道中沒有發送方發送數據,接收方也會發生阻塞,直到發送方發送數據爲止。

③ 每次接收一個元素。
通道一次只能接收一個數據元素。

通道的數據接收一共有如下 4 種寫法。

1) 阻塞接收數據

阻塞模式接收數據時,將接收變量做爲<-操做符的左值,格式以下:

data := <-ch

 

package main

import (
	"fmt"
)

func main() {
	// 構建一個通道
	ch := make(chan int)
	// 開啓一個併發匿名函數
	go func() {
		fmt.Println("start goroutine")
		// 經過通道通知main的goroutine
		ch <- 0
		fmt.Println("exit goroutine")
	}()
	fmt.Println("wait goroutine")
	// 等待匿名goroutine

	data := <-ch
	//<-ch
	fmt.Println(data)
}
  

  

 

執行該語句時將會阻塞,直到接收到數據並賦值給 data 變量。

2) 非阻塞接收數據

使用非阻塞方式從通道接收數據時,語句不會發生阻塞,格式以下:

data, ok := <-ch

  • data:表示接收到的數據。未接收到數據時,data 爲通道類型的零值。
  • ok:表示是否接收到數據。

 

package main

import (
	"fmt"
)

func main() {
	// 構建一個通道
	ch := make(chan int)
	// 開啓一個併發匿名函數
	go func() {
		fmt.Println("start goroutine")
		// 經過通道通知main的goroutine
		ch <- 0
		fmt.Println("exit goroutine")
	}()
	fmt.Println("wait goroutine")
	// 等待匿名goroutine
	data, ok := <-ch
	if !ok {
		fmt.Println("no data")
	} else {
		fmt.Println(data)
	}
	//data := <-ch
	//<-ch

}

  

 

非阻塞的通道接收方法可能形成高的 CPU 佔用,所以使用很是少。若是須要實現接收超時檢測,能夠配合 select 和計時器 channel 進行,能夠參見後面的內容。

3) 接收任意數據,忽略接收的數據

阻塞接收數據後,忽略從通道返回的數據,格式以下:

<-ch

執行該語句時將會發生阻塞,直到接收到數據,但接收到的數據會被忽略。這個方式實際上只是經過通道在 goroutine 間阻塞收發實現併發同步。

package main

import (
	"fmt"
)

func main() {
	// 構建一個通道
	ch := make(chan int)
	// 開啓一個併發匿名函數
	go func() {
		fmt.Println("start goroutine")
		// 經過通道通知main的goroutine
		ch <- 0
		fmt.Println("exit goroutine")
	}()
	fmt.Println("wait goroutine")
	// 等待匿名goroutine
	<-ch
	fmt.Println("all done")
}

4) 循環接收

通道的數據接收能夠借用 for range 語句進行多個元素的接收操做,格式以下:

for data := range ch {
}

  

package main

import (
	"fmt"
	"time"
)

func main() {
	// 構建一個通道
	ch := make(chan int)
	// 開啓一個併發匿名函數
	go func() {
		// 從3循環到0
		for i := 3; i >= 0; i-- {
			// 發送3到0之間的數值
			ch <- i
			// 每次發送完時等待
			time.Sleep(time.Second)
		}
	}()
	// 遍歷接收通道數據
	for data := range ch {
		// 打印通道數據
		fmt.Println(data)
		// 當遇到數據0時, 退出接收循環
		if data == 0 {
			break
		}
	}
}

  代碼說明以下:

經過 make 生成一個整型元素的通道。

將匿名函數併發執行。

用循環生成 3 到 0 之間的數值。

將 3 到 0 之間的數值依次發送到通道 ch 中。

每次發送後暫停 1 秒。

使用 for 從通道中接收數據。

將接收到的數據打印出來。

當接收到數值 0 時,中止接收。若是繼續發送,因爲接收 goroutine 已經退出,沒有 goroutine 發送到通道,所以運行時將會觸發宕機報錯。

 

關閉 chan

close(chan)

  

注意:

  • close 之後不能再寫入,寫入會出現 panic
  • 重複 close 會出現 panic
  • 只讀的 chan 不能 close
  • close 之後還能夠讀取數據
相關文章
相關標籤/搜索