Go語言 Channel <- 箭頭操做符 詳解

Channel是Go中的一個核心類型,你能夠把它當作一個管道,經過它併發核心單元就能夠發送或者接收數據進行通信(communication)。express

它的操做符是箭頭 <- 。緩存

ch <- v    // 發送值v到Channel ch中
v := <-ch  // 從Channel ch中接收數據,並將數據賦值給v

(箭頭的指向就是數據的流向)併發

就像 map 和 slice 數據類型同樣, channel必須先建立再使用:dom

ch := make(chan int)

Channel類型

 

Channel類型的定義格式以下:性能

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

它包括三種類型的定義。可選的<-表明channel的方向。若是沒有指定方向,那麼Channel就是雙向的,既能夠接收數據,也能夠發送數據。ui

chan T          // 能夠接收和發送類型爲 T 的數據
chan<- float64  // 只能夠用來發送 float64 類型的數據
<-chan int      // 只能夠用來接收 int 類型的數據

<-老是優先和最左邊的類型結合。(The <- operator associates with the leftmost chan possible)lua

chan<- chan int    // 等價 chan<- (chan int)
chan<- <-chan int  // 等價 chan<- (<-chan int)
<-chan <-chan int  // 等價 <-chan (<-chan int)
chan (<-chan int)

使用make初始化Channel,而且能夠設置容量:spa

make(chan int, 100)

容量(capacity)表明Channel容納的最多的元素的數量,表明Channel的緩存的大小。
若是沒有設置容量,或者容量設置爲0, 說明Channel沒有緩存,只有sender和receiver都準備好了後它們的通信(communication)纔會發生(Blocking)。若是設置了緩存,就有可能不發生阻塞, 只有buffer滿了後 send纔會阻塞, 而只有緩存空了後receive纔會阻塞。一個nil channel不會通訊。code

能夠經過內建的close方法能夠關閉Channel。隊列

你能夠在多個goroutine從/往 一個channel 中 receive/send 數據, 沒必要考慮額外的同步措施。

Channel能夠做爲一個先入先出(FIFO)的隊列,接收的數據和發送的數據的順序是一致的。

channel的 receive支持 multi-valued assignment,如

v, ok := <-ch

它能夠用來檢查Channel是否已經被關閉了。

  1. send語句
    send語句用來往Channel中發送數據, 如ch <- 3
    它的定義以下:
SendStmt = Channel "<-" Expression .
Channel  = Expression .

在通信(communication)開始前channel和expression必選先求值出來(evaluated),好比下面的(3+4)先計算出7而後再發送給channel。

c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)

send被執行前(proceed)通信(communication)一直被阻塞着。如前所言,無緩存的channel只有在receiver準備好後send才被執行。若是有緩存,而且緩存未滿,則send會被執行。

往一個已經被close的channel中繼續發送數據會致使run-time panic。

往nil channel中發送數據會一致被阻塞着。

  1. receive 操做符
    <-ch用來從channel ch中接收數據,這個表達式會一直被block,直到有數據能夠接收。

從一個nil channel中接收數據會一直被block。

從一個被close的channel中接收數據不會被阻塞,而是當即返回,接收完已發送的數據後會返回元素類型的零值(zero value)。

如前所述,你可使用一個額外的返回參數來檢查channel是否關閉。

x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch

若是OK 是false,代表接收的x是產生的零值,這個channel被關閉了或者爲空。

 

 

blocking

缺省狀況下,發送和接收會一直阻塞着,直到另外一方準備好。這種方式能夠用來在gororutine中進行同步,而沒必要使用顯示的鎖或者條件變量。

如官方的例子中x, y := <-c, <-c這句會一直等待計算結果發送到channel中。

import "fmt"
func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	c <- sum // send sum to c
}
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	x, y := <-c, <-c // receive from c
	fmt.Println(x, y, x+y)
}

Buffered Channels

 

make的第二個參數指定緩存的大小:ch := make(chan int, 100)

經過緩存的使用,能夠儘可能避免阻塞,提供應用的性能。

Range

for …… range語句能夠處理Channel。

func main() {
	go func() {
		time.Sleep(1 * time.Hour)
	}()
	c := make(chan int)
	go func() {
		for i := 0; i < 10; i = i + 1 {
			c <- i
		}
		close(c)
	}()
	for i := range c {
		fmt.Println(i)
	}
	fmt.Println("Finished")
}

range c產生的迭代值爲Channel中發送的值,它會一直迭代直到channel被關閉。上面的例子中若是把close(c)註釋掉,程序會一直阻塞在for …… range那一行。

 

 

select

select語句選擇一組可能的send操做和receive操做去處理。它相似switch,可是隻是用來處理通信(communication)操做。
它的case能夠是send語句,也能夠是receive語句,亦或者default

receive語句能夠將值賦值給一個或者兩個變量。它必須是一個receive操做。

最多容許有一個default case,它能夠放在case列表的任何位置,儘管咱們大部分會將它放在最後。

import "fmt"
func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}
func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacci(c, quit)
}

若是有同時多個case去處理,好比同時有多個channel能夠接收數據,那麼Go會僞隨機的選擇一個case處理(pseudo-random)。若是沒有case須要處理,則會選擇default去處理,若是default case存在的狀況下。若是沒有default case,則select語句會阻塞,直到某個case須要處理。

須要注意的是,nil channel上的操做會一直被阻塞,若是沒有default case,只有nil channel的select會一直被阻塞。

select語句和switch語句同樣,它不是循環,它只會選擇一個case來處理,若是想一直處理channel,你能夠在外面加一個無限的for循環:

for {
	select {
	case c <- x:
		x, y = y, x+y
	case <-quit:
		fmt.Println("quit")
		return
	}
}

timeout

 

select有很重要的一個應用就是超時處理。 由於上面咱們提到,若是沒有case須要處理,select語句就會一直阻塞着。這時候咱們可能就須要一個超時操做,用來處理超時的狀況。
下面這個例子咱們會在2秒後往channel c1中發送一個數據,可是select設置爲1秒超時,所以咱們會打印出timeout 1,而不是result 1

import "time"
import "fmt"
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "result 1"
    }()
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 1")
    }
}

其實它利用的是time.After方法,它返回一個類型爲<-chan Time的單向的channel,在指定的時間發送一個當前時間給返回的channel中。

 

Timer和Ticker

咱們看一下關於時間的兩個Channel。
timer是一個定時器,表明將來的一個單一事件,你能夠告訴timer你要等待多長時間,它提供一個Channel,在未來的那個時間那個Channel提供了一個時間值。下面的例子中第二行會阻塞2秒鐘左右的時間,直到時間到了纔會繼續執行。

timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")

固然若是你只是想單純的等待的話,可使用time.Sleep來實現。

你還可使用timer.Stop來中止計時器。

timer2 := time.NewTimer(time.Second)
go func() {
	<-timer2.C
	fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
	fmt.Println("Timer 2 stopped")
}

ticker是一個定時觸發的計時器,它會以一個間隔(interval)往Channel發送一個事件(當前時間),而Channel的接收者能夠以固定的時間間隔從Channel中讀取事件。下面的例子中ticker每500毫秒觸發一次,你能夠觀察輸出的時間。

ticker := time.NewTicker(time.Millisecond * 500)
go func() {
	for t := range ticker.C {
		fmt.Println("Tick at", t)
	}
}()

相似timer, ticker也能夠經過Stop方法來中止。一旦它中止,接收者再也不會從channel中接收數據了。

 

close

內建的close方法能夠用來關閉channel。

總結一下channel關閉後sender的receiver操做。
若是channel c已經被關閉,繼續往它發送數據會致使panic: send on closed channel:

import "time"
func main() {
	go func() {
		time.Sleep(time.Hour)
	}()
	c := make(chan int, 10)
	c <- 1
	c <- 2
	close(c)
	c <- 3
}

可是從這個關閉的channel中不但能夠讀取出已發送的數據,還能夠不斷的讀取零值:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
fmt.Println(<-c) //1
fmt.Println(<-c) //2
fmt.Println(<-c) //0
fmt.Println(<-c) //0

可是若是經過range讀取,channel關閉後for循環會跳出:

c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
	fmt.Println(i)
}

經過i, ok := <-c能夠查看Channel的狀態,判斷值是零值仍是正常讀取的值。

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

同步

 

channel能夠用在goroutine之間的同步。
下面的例子中main goroutine經過done channel等待worker完成任務。 worker作完任務後只需往channel發送一個數據就能夠通知main goroutine任務完成。

import (
	"fmt"
	"time"
)
func worker(done chan bool) {
	time.Sleep(time.Second)
	// 通知任務已完成
	done <- true
}
func main() {
	done := make(chan bool, 1)
	go worker(done)
	// 等待任務完成
	<-done
}
相關文章
相關標籤/搜索