channel

一.基本語法

c := make(chan bool) //建立一個無緩衝的bool型Channel
c <- x        //向一個Channel發送一個值
<- c          //從一個Channel中接收一個值
x = <- c      //從Channel c接收一個值並將其存儲到x中
x, ok = <- c  //從Channel接收一個值,若是channel關閉了或沒有數據,那麼ok將被置爲false
ch chan int //可讀寫
ch1 chan<- int  //ch1只能寫
ch2 <-chan int  //ch2只能讀
channel是類型相關的,也就是一個channel只能傳遞一種類型

  

二.爲何要使用channel

goroutine是Go語言中的輕量級線程實現,由Go運行時(runtime)管理.
先看一個例子:緩存

func Sub(i int) {
	fmt.Println("from sub func", i)
}

func main() {
	for i := 0; i < 5; i++ {
		Sub(i)
	}
	fmt.Println("from main")
}

這個例子作了一件事情,在main函數中串行執行了5次Sub函數.安全

若是咱們須要Sub函數可以併發的執行,咱們加個go,將每個Sub函數放在goroutine中去(main函數其實也是個goroutine),代碼以下所示:併發

func Sub(i int) {
	fmt.Println("from sub func", i)
}

func main() {
	for i := 0; i < 5; i++ {
		go Sub(i)
	}
	fmt.Println("from main")
}

  

編譯執行,你會發現只打印出了from main,Sub函數中字符並無打印出來.這是由於主函數main啓動了5個Sub函數後,並無等待它們完成即退出了!這顯然不是咱們要的結果,咱們使用go提供的消息通訊機制channel來重構代碼,保證Sub函數執行完成後,主函數再退出!函數

 

三.無緩存channel(信道,這個翻譯較爲接近其英文表達的意思)

channel(信道)是什麼,簡單說,是goroutine之間互相通信的東西。相似咱們Unix上的管道(能夠在進程間傳遞消息), 用來goroutine之間發消息和接收消息。其實,就是在作goroutine之間的內存共享。spa

func Sub(ch chan int) {
	for i := 0; i < 5; i++ {
		fmt.Println("from sub func", i)
	}
	ch <- 1 // 向channel存消息,若是沒有其餘goroutine取走數據,那麼掛起Sub
}

func main() {
	ch := make(chan int) // 建立了一個無緩存的channel
	go Sub(ch) // 開啓一個goroutine來執行Sub
	fmt.Println("from main")
	<-ch // 從channel取消息,若是沒有寫入消息,掛起main
}

  

無緩衝的信道在取消息和存消息的時候都會掛起當前的goroutine.線程

因此上面代碼的執行流程是:
建立ch信道
新開goroutine來執行Sub,但由於ch信道中寫入的消息沒有被取走,Sub掛起.
from main被打印出來.
<-ch 掛起了main,直到取到數據,這就保證了Sub完成後,main才結束.翻譯

因此無緩存信道,存入數據必須取走,只存不取或取空的數據,都將致使死鎖.blog

如下情形都是死鎖:隊列

//取不存在的消息:
ch := make(chan int)
<-ch

//只寫不取:
ch := make(chan int)
ch <- 1

//多個channel,ch1等待ch2的消息,但ch2沒有消息寫入:
ch1 := make(chan int)
ch2 := make(chan int)
ch1 <- <-ch2
<-ch1

  

四.帶緩存的channel

無緩存信道只負責流通消息,任何對該信道的讀和寫,都阻塞信道.
帶緩存的channel,不只能夠流通數據,還能夠緩存數據,只有達到緩存最大數目後,也就是緩存滿了後,才阻塞信道,這聽起來很像隊列(Queue).其實,緩衝信道是先進先出的,咱們能夠把緩衝信道看做爲一個線程安全的隊列進程

ch := make(chan int, 2) // 帶緩存channel,緩存數目2個,放入2個數據,不會掛起,只有放入第3個的時候才掛起當前goroutine

示例代碼:

 

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
}

  

對ch的寫入,由於沒有超過緩存數目3,因此不會阻塞;對ch取數據,將按先進先出,依次輸出1 2 3

你會發現,帶緩存的信道,取數據仍是挺麻煩的.咱們用for range來取數據

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 4

	for v := range ch {
		fmt.Println(v)
	}
}

  

報出deadlock,緣由是ch沒有關閉的情形下,range一直在讀取.加一個判斷:

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 4

	for v := range ch {
		fmt.Println(v)
		if len(ch) <= 0 {
			break
		}
	}
}

  

咱們判斷了ch中有沒有數據,沒有數據就跳出循環.能夠正常輸出.固然咱們也能夠顯式關閉信道.

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 4

	close(ch) // 關閉信道

	for v := range ch {
		fmt.Println(v)
	}
}

  

五.執行多個goroutine

1.無緩存

const MAX = 1000

var ch chan int

func Sub(i int) {
	fmt.Println(i)
	ch <- 1
}

func main() {
	ch = make(chan int)
	for i := 0; i < MAX; i++ {
		go Sub(i)
	}

	for i := 0; i < MAX; i++ {
		<-ch
	}
}

  

2.帶緩存

const MAX = 1000

var ch chan int

func Sub(i int) {
	fmt.Println(i)
	ch <- 1
}

func main() {
	ch = make(chan int, MAX)
	for i := 0; i < MAX; i++ {
		go Sub(i)
	}

	for i := 0; i < MAX; i++ {
		<-ch
	}
}

  

二者效果相同,不一樣點在於:
無緩衝的信道是一批數據一個一個的流進流出
緩衝信道則是一個一個存儲,而後一塊兒流出去

 

六.單向channel

顧名思義,單向channel只能用於發送或者接收數據。channel自己必然是同時支持讀寫的,不然根本無法用。假如一個channel真的只能讀,那麼確定只會是空的,由於你沒機會往裏面寫數據。同理,若是一個channel只容許寫,即便寫進去了,也沒有絲毫意義,由於沒有機會讀取裏面的數據。所謂的單向channel概念,其實只是對channel的一種使用限制。

ch1 chan<- int //ch1只能寫
ch2 <-chan int //ch2只能讀

示例:
func Parse(ch <-chan int) {
	for value := range ch {
		fmt.Println("Parsing value", value)
	}
}
相關文章
相關標籤/搜索