溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go語言之通道

發(fā)布時間:2020-07-25 14:51:39 來源:網(wǎng)絡(luò) 閱讀:415 作者:baby神 欄目:編程語言

上一篇我們講的原子函數(shù)和互斥鎖,都可以保證共享數(shù)據(jù)的讀寫。但是呢,它們還是有點復(fù)雜,而且影響性能。對此,Go又為我們提供了一種工具,這就是通道。


所以在多個goroutine并發(fā)中,我們不僅可以通過原子函數(shù)和互斥鎖保證對共享資源的安全訪問,消除競爭的狀態(tài),還可以通過使用通道,在多個goroutine發(fā)送和接受共享的數(shù)據(jù),達到數(shù)據(jù)同步的目的。


通道,它有點像在兩個routine之間架設(shè)的管道:一個goroutine可以往這個管道里塞數(shù)據(jù),另外一個可以從這個管道里取數(shù)據(jù)。有點類似于我們說的隊列。


聲明一個通道很簡單,我們使用chan關(guān)鍵字即可。除此之外,還要指定通道中發(fā)送和接收數(shù)據(jù)的類型,這樣我們才能知道,要發(fā)送什么類型的數(shù)據(jù)給通道,也知道從這個通道里可以接收到什么類型的數(shù)據(jù)。


ch:=make(chan int)


通道類型和Map這些類型一樣,可以使用內(nèi)置的make函數(shù)聲明初始化。這里我們初始化了一個chan int類型的通道,所以我們只能往這個通道里發(fā)送int類型的數(shù)據(jù),當然接收也只能是int類型的數(shù)據(jù)。


我們知道,通道是用于在goroutine之間通信的,它具有發(fā)送和接收兩個操作,而且這兩個操作的運算符都是<-。


ch <- 2 //發(fā)送數(shù)值2給這個通道
x:=<-ch //從通道里讀取值,并把讀取的值賦值給x變量
<-ch //從通道里讀取值,然后忽略


看例子,慢慢理解發(fā)送和接收的用法。發(fā)送操作<-在通道的后面,看箭頭方向,表示把數(shù)值 2 發(fā)送到通道ch里;接收操作<-在通道的前面,而且是一個一元操作符,看箭頭方向,表示從通道ch里讀取數(shù)據(jù)。讀取的數(shù)據(jù)可以賦值給一個變量,也可以忽略。


通道我們還可以使用內(nèi)置的close函數(shù)關(guān)閉。


close(ch)


如果一個通道被關(guān)閉了,我們就不能往這個通道里發(fā)送數(shù)據(jù)了,如果發(fā)送的話,會引起painc異常。但是,我們還可以接收通道里的數(shù)據(jù),如果通道里沒有數(shù)據(jù)的話,接收的數(shù)據(jù)是nil。


剛剛我們使用make函數(shù)初始化的時候,只有一個參數(shù),其實make還可以有第二個參數(shù),用于指定通道的大小。默認沒有第二個參數(shù)的時候,通道的大小為 0 ,這種通道也被成為無緩沖通道


ch:=make(chan int)
ch:=make(chan int,0)
ch:=make(chan int,2)


看例子,其中第一個和第二個初始化是等價的。第三個初始化創(chuàng)建了一個大小為 2 的通道,這種稱為有緩沖通道。


無緩沖的通道


無緩沖的通道指的是通道的大小為 0 。也就是說,這種類型的通道在接收前沒有能力保存任何值,它要求發(fā)送goroutine和接收goroutine同時準備好,才可以完成發(fā)送和接收操作。


從上面無緩沖的通道定義來看,發(fā)送goroutine和接收gouroutine必須是同步的。同時準備后,如果沒有同時準備好的話,先執(zhí)行的操作就會阻塞等待,直到另一個相對應(yīng)的操作準備好為止。這種無緩沖的通道我們也稱之為同步通道。


func main() {
    ch := make(chan int)
    go func() {
            var sum int = 0
            for i := 0; i < 10; i++ {
                sum += i            }
            ch <- sum    }()

    fmt.Println(<-ch)}


在前面的例子中,我們?yōu)榱搜菔緂oroutine,防止程序提前終止,都是使用sync.WaitGroup進行等待。現(xiàn)在的這個例子就不用了,我們使用同步通道來等待。


在計算sum和的goroutine沒有執(zhí)行完,把值賦給ch通道之前,fmt.Println(<-ch)會一直等待,所以main主goroutine就不會終止。只有當計算和的goroutine完成,并且發(fā)送到ch通道的操作準備好后,同時<-ch就會接收計算好的值,然后打印出來。


管道


我們在使用Bash的時候,有個管道操作|。它的意思是把上一個操作的輸出,當成下一個操作的輸入,連起來,做一連串的處理操作。


  ~ ls |grep 'D'
Desktop
Documents
Downloads


比如上面這個例子的意思是,先使用ls命令,把當前目錄下的目錄和文件列出來,作為下一個grep命令的輸入,然后通過grep命令,匹配我們需要顯示的目錄和文件,這里匹配以D開頭的文件名或者目錄名。


其實我們使用通道也可以做到管道的效果,我們只需要把一個通道的輸出,當成下一個通道的輸入即可。


func main() {
    one := make(chan int)
    two := make(chan int)
    go func() {
        one<-100
    }()
    go func() {
        v:=<-one
        two<-v    }()

    fmt.Println(<-two)}


這里例子中我們定義兩個通道onetwo,然后按照順序,先把 100 發(fā)送給通道one,然后用另外一個goroutine從one接收值,再發(fā)送給通道two,最終在主goroutine里等著接收打印two通道里的值。這就類似于一個管道的操作,把通道one的輸出,當成通道two的輸入,類似于接力賽一樣。


有緩沖的通道


有緩沖通道,其實是一個隊列,這個隊列的最大容量就是我們使用make函數(shù)創(chuàng)建通道時,通過第二個參數(shù)指定的。


ch := make(chan int, 3)


這里創(chuàng)建容量為 3 的、有緩沖的通道。對于有緩沖的通道,向其發(fā)送操作就是向隊列的尾部插入元素,接收操作則是從隊列的頭部刪除元素,并返回這個剛剛刪除的元素。


當隊列滿的時候,發(fā)送操作會阻塞;當隊列空的時候,接受操作會阻塞。有緩沖的通道,不要求發(fā)送和接收操作是同步的,相反可以解耦發(fā)送和接收操作。


想知道通道的容量以及里面有幾個元素數(shù)據(jù)怎么辦?其實和map一樣,使用caplen函數(shù)就可以了。


cap(ch)
len(ch)


cap函數(shù)返回通道的最大容量,len函數(shù)返回現(xiàn)在通道里有幾個元素。


func mirroredQuery() string {
    responses := make(chan string, 3)
    go func() { responses <- request("asia.gopl.io") }()
    go func() { responses <- request("europe.gopl.io") }()    
    go func() { responses <- request("americas.gopl.io") }()    
    return <-responses // return the quickest response}func request(hostname string) (response string) { /* ... */ }


這是Go語言圣經(jīng)里比較有意義的一個例子,例子是想獲取服務(wù)端的一個數(shù)據(jù),不過這個數(shù)據(jù)在三個鏡像站點上都存在,這三個鏡像分散在不同的地理位置,而我們的目的又是想最快地獲取數(shù)據(jù)。


所以這里,我們定義了一個容量為 3 的通道responses,然后同時發(fā)起 3 個并發(fā)goroutine向這三個鏡像獲取數(shù)據(jù),獲取到的數(shù)據(jù)發(fā)送到通道responses中,最后我們使用return <-responses返回獲取到的第一個數(shù)據(jù),也就是最快返回的那個鏡像的數(shù)據(jù)。


單向通道


有時候,我們有一些特殊場景,比如限制一個通道只可以接收,但是不能發(fā)送;有時候限制一個通道只能發(fā)送,但是不能接收,這種通道我們稱為單向通道


定義單向通道也很簡單,只需要在定義的時候,帶上<-即可。


var send chan<- int //只能發(fā)送
var receive <-chan int //只能接收


注意<-操作符的位置,在后面是只能發(fā)送,對應(yīng)發(fā)送操作;在前面是只能接收,對應(yīng)接收操作。


單向通道應(yīng)用于函數(shù)或者方法的參數(shù)比較多,比如:


func counter(out chan<- int) {
}


例子這樣的,只能進行發(fā)送操作,防止使用接收操作。如果使用了接收操作,在編譯的時候就會報錯的。


使用通道可以很簡單地在goroutine之間共享數(shù)據(jù),下一篇會具體介紹一些例子,以便更好地理解并發(fā)。


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI