佳木斯湛栽影视文化发展公司

主頁 > 知識(shí)庫 > Go語言中 Channel 詳解

Go語言中 Channel 詳解

熱門標(biāo)簽:網(wǎng)站排名優(yōu)化 地方門戶網(wǎng)站 呼叫中心市場(chǎng)需求 AI電銷 服務(wù)外包 Linux服務(wù)器 百度競(jìng)價(jià)排名 鐵路電話系統(tǒng)

Channel是Go中的一個(gè)核心類型,你可以把它看成一個(gè)管道,通過它并發(fā)核心單元就可以發(fā)送或者接收數(shù)據(jù)進(jìn)行通訊(communication)。

它的操作符是箭頭 - 。

ch - v    // 發(fā)送值v到Channel ch中
v := -ch  // 從Channel ch中接收數(shù)據(jù),并將數(shù)據(jù)賦值給v
(箭頭的指向就是數(shù)據(jù)的流向)

就像 map 和 slice 數(shù)據(jù)類型一樣, channel必須先創(chuàng)建再使用:

ch := make(chan int)
Channel類型
Channel類型的定義格式如下:

ChannelType = ( "chan" | "chan" "-" | "-" "chan" ) ElementType .
它包括三種類型的定義??蛇x的-代表channel的方向。如果沒有指定方向,那么Channel就是雙向的,既可以接收數(shù)據(jù),也可以發(fā)送數(shù)據(jù)。

chan T          // 可以接收和發(fā)送類型為 T 的數(shù)據(jù)
chan- float64  // 只可以用來發(fā)送 float64 類型的數(shù)據(jù)
-chan int      // 只可以用來接收 int 類型的數(shù)據(jù)
-總是優(yōu)先和最左邊的類型結(jié)合。(The - operator associates with the leftmost chan possible)

chan- chan int    // 等價(jià) chan- (chan int)
chan- -chan int  // 等價(jià) chan- (-chan int)
-chan -chan int  // 等價(jià) -chan (-chan int)
chan (-chan int)

使用make初始化Channel,并且可以設(shè)置容量:

make(chan int, 100)

容量(capacity)代表Channel容納的最多的元素的數(shù)量,代表Channel的緩存的大小。
如果沒有設(shè)置容量,或者容量設(shè)置為0, 說明Channel沒有緩存,只有sender和receiver都準(zhǔn)備好了后它們的通訊(communication)才會(huì)發(fā)生(Blocking)。如果設(shè)置了緩存,就有可能不發(fā)生阻塞, 只有buffer滿了后 send才會(huì)阻塞, 而只有緩存空了后receive才會(huì)阻塞。一個(gè)nil channel不會(huì)通信。

可以通過內(nèi)建的close方法可以關(guān)閉Channel。

你可以在多個(gè)goroutine從/往 一個(gè)channel 中 receive/send 數(shù)據(jù), 不必考慮額外的同步措施。

Channel可以作為一個(gè)先入先出(FIFO)的隊(duì)列,接收的數(shù)據(jù)和發(fā)送的數(shù)據(jù)的順序是一致的。

channel的 receive支持 multi-valued assignment,如

v, ok := -ch
它可以用來檢查Channel是否已經(jīng)被關(guān)閉了。

send語句

send語句用來往Channel中發(fā)送數(shù)據(jù), 如ch - 3。
它的定義如下:

SendStmt = Channel "-" Expression .
Channel  = Expression .

在通訊(communication)開始前channel和expression必選先求值出來(evaluated),比如下面的(3+4)先計(jì)算出7然后再發(fā)送給channel。

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

send被執(zhí)行前(proceed)通訊(communication)一直被阻塞著。如前所言,無緩存的channel只有在receiver準(zhǔn)備好后send才被執(zhí)行。如果有緩存,并且緩存未滿,則send會(huì)被執(zhí)行。

往一個(gè)已經(jīng)被close的channel中繼續(xù)發(fā)送數(shù)據(jù)會(huì)導(dǎo)致run-time panic。

往nil channel中發(fā)送數(shù)據(jù)會(huì)一致被阻塞著。

receive 操作符

-ch用來從channel ch中接收數(shù)據(jù),這個(gè)表達(dá)式會(huì)一直被block,直到有數(shù)據(jù)可以接收。
從一個(gè)nil channel中接收數(shù)據(jù)會(huì)一直被block。

從一個(gè)被close的channel中接收數(shù)據(jù)不會(huì)被阻塞,而是立即返回,接收完已發(fā)送的數(shù)據(jù)后會(huì)返回元素類型的零值(zero value)。

如前所述,你可以使用一個(gè)額外的返回參數(shù)來檢查channel是否關(guān)閉。

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

如果OK 是false,表明接收的x是產(chǎn)生的零值,這個(gè)channel被關(guān)閉了或者為空。

blocking

缺省情況下,發(fā)送和接收會(huì)一直阻塞著,直到另一方準(zhǔn)備好。這種方式可以用來在gororutine中進(jìn)行同步,而不必使用顯示的鎖或者條件變量。

如官方的例子中x, y := -c, -c這句會(huì)一直等待計(jì)算結(jié)果發(fā)送到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的第二個(gè)參數(shù)指定緩存的大?。篶h := make(chan int, 100)。

通過緩存的使用,可以盡量避免阻塞,提供應(yīng)用的性能。

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產(chǎn)生的迭代值為Channel中發(fā)送的值,它會(huì)一直迭代直到channel被關(guān)閉。上面的例子中如果把close(c)注釋掉,程序會(huì)一直阻塞在for …… range那一行。

select

select語句選擇一組可能的send操作和receive操作去處理。它類似switch,但是只是用來處理通訊(communication)操作。
它的case可以是send語句,也可以是receive語句,亦或者default。

receive語句可以將值賦值給一個(gè)或者兩個(gè)變量。它必須是一個(gè)receive操作。

最多允許有一個(gè)default case,它可以放在case列表的任何位置,盡管我們大部分會(huì)將它放在最后。

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)
}

如果有同時(shí)多個(gè)case去處理,比如同時(shí)有多個(gè)channel可以接收數(shù)據(jù),那么Go會(huì)偽隨機(jī)的選擇一個(gè)case處理(pseudo-random)。如果沒有case需要處理,則會(huì)選擇default去處理,如果default case存在的情況下。如果沒有default case,則select語句會(huì)阻塞,直到某個(gè)case需要處理。

需要注意的是,nil channel上的操作會(huì)一直被阻塞,如果沒有default case,只有nil channel的select會(huì)一直被阻塞。

select語句和switch語句一樣,它不是循環(huán),它只會(huì)選擇一個(gè)case來處理,如果想一直處理channel,你可以在外面加一個(gè)無限的for循環(huán):

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

timeout

select有很重要的一個(gè)應(yīng)用就是超時(shí)處理。 因?yàn)樯厦嫖覀兲岬?,如果沒有case需要處理,select語句就會(huì)一直阻塞著。這時(shí)候我們可能就需要一個(gè)超時(shí)操作,用來處理超時(shí)的情況。
下面這個(gè)例子我們會(huì)在2秒后往channel c1中發(fā)送一個(gè)數(shù)據(jù),但是select設(shè)置為1秒超時(shí),因此我們會(huì)打印出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")
    }
}

其實(shí)它利用的是time.After方法,它返回一個(gè)類型為-chan Time的單向的channel,在指定的時(shí)間發(fā)送一個(gè)當(dāng)前時(shí)間給返回的channel中。

Timer和Ticker

我們看一下關(guān)于時(shí)間的兩個(gè)Channel。
timer是一個(gè)定時(shí)器,代表未來的一個(gè)單一事件,你可以告訴timer你要等待多長(zhǎng)時(shí)間,它提供一個(gè)Channel,在將來的那個(gè)時(shí)間那個(gè)Channel提供了一個(gè)時(shí)間值。下面的例子中第二行會(huì)阻塞2秒鐘左右的時(shí)間,直到時(shí)間到了才會(huì)繼續(xù)執(zhí)行。

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

當(dāng)然如果你只是想單純的等待的話,可以使用time.Sleep來實(shí)現(xiàn)。

你還可以使用timer.Stop來停止計(jì)時(shí)器。

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是一個(gè)定時(shí)觸發(fā)的計(jì)時(shí)器,它會(huì)以一個(gè)間隔(interval)往Channel發(fā)送一個(gè)事件(當(dāng)前時(shí)間),而Channel的接收者可以以固定的時(shí)間間隔從Channel中讀取事件。下面的例子中ticker每500毫秒觸發(fā)一次,你可以觀察輸出的時(shí)間。

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

類似timer, ticker也可以通過Stop方法來停止。一旦它停止,接收者不再會(huì)從channel中接收數(shù)據(jù)了。

close

內(nèi)建的close方法可以用來關(guān)閉channel。

總結(jié)一下channel關(guān)閉后sender的receiver操作。
如果channel c已經(jīng)被關(guān)閉,繼續(xù)往它發(fā)送數(shù)據(jù)會(huì)導(dǎo)致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
}

但是從這個(gè)關(guān)閉的channel中不但可以讀取出已發(fā)送的數(shù)據(jù),還可以不斷的讀取零值:

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關(guān)閉后for循環(huán)會(huì)跳出:

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

通過i, ok := -c可以查看Channel的狀態(tài),判斷值是零值還是正常讀取的值。

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完成任務(wù)。 worker做完任務(wù)后只需往channel發(fā)送一個(gè)數(shù)據(jù)就可以通知main goroutine任務(wù)完成。

import (
 "fmt"
 "time"
)
func worker(done chan bool) {
 time.Sleep(time.Second)
 // 通知任務(wù)已完成
 done - true
}
func main() {
 done := make(chan bool, 1)
 go worker(done)
 // 等待任務(wù)完成
 -done
}

您可能感興趣的文章:
  • golang判斷chan channel是否關(guān)閉的方法
  • Golang中channel使用的一些小技巧
  • Go語言的管道Channel用法實(shí)例
  • Golang優(yōu)雅關(guān)閉channel的方法示例
  • golang中單向channel的語法介紹
  • 淺談Go Channel 高級(jí)實(shí)踐
  • Go中Channel發(fā)送和接收操作指南

標(biāo)簽:崇左 衡水 仙桃 黃山 湖南 湘潭 銅川 蘭州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Go語言中 Channel 詳解》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    宝鸡市| 贵定县| 噶尔县| 简阳市| 昭觉县| 萍乡市| 宣城市| 五常市| 尉犁县| 汶川县| 双江| 龙游县| 商水县| 阿尔山市| 新晃| 张家界市| 河南省| 通河县| 哈尔滨市| 乡宁县| 六枝特区| 曲麻莱县| 收藏| 娄底市| 永川市| 缙云县| 马龙县| 道孚县| 南康市| 汾西县| 内乡县| 周宁县| 陆川县| 洛宁县| 仙游县| 西昌市| 东阳市| 德庆县| 石柱| 贵定县| 拜城县|