1. 程式人生 > >GO語言學習(四)---chan

GO語言學習(四)---chan

chan相關知識

程式碼片段一—chan用於兩個goruntime之間通訊

原始碼

//測試chan
package main

import "fmt"
import "time"

var strChan = make(chan string,3)

func main(){
    syncChan1 := make(chan struct{},1)  //接收同步變數  
    syncChan2 := make(chan struct{},2) //主執行緒啟動了兩個goruntime執行緒,
                                       //等這兩個goruntime執行緒結束後主執行緒才能結束
//用於演示接受操作 go func(){ <- syncChan1 //表示可以開始接收資料了,否則等待 fmt.Println("[receiver] Received a sync signal and wait a second...") time.Sleep(time.Second) for{ if elem,ok := <-strChan;ok{ fmt.Println("[receiver] Received:",elem) }else
{ break } } fmt.Println("[receiver] Stopped.") syncChan2 <- struct{}{} }() //用於演示傳送操作 go func(){ for i,elem := range []string{"a","b","c","d"}{ fmt.Println("[sender] Sent:",elem) strChan <- elem if
(i+1)%3==0 { syncChan1 <- struct{}{} fmt.Println("[sender] Sent a sync signal. wait 1 secnd...") time.Sleep(time.Second) } } fmt.Println("[sender] wait 2 seconds...") time.Sleep(time.Second) close(strChan) syncChan2 <- struct{}{} }() //主執行緒等待發送執行緒和接收執行緒結束後再結束 fmt.Println("[main] waiting...") <- syncChan2 <- syncChan2 fmt.Println("[main] stoped") }

執行結果

[main] waiting...
[sender] Sent: a
[sender] Sent: b
[sender] Sent: c
[sender] Sent a sync signal. wait 1 secnd...
[receiver] Received a sync signal and wait a second...
[receiver] Received: a
[receiver] Received: b
[receiver] Received: c
[sender] Sent: d
[sender] wait 2 seconds...
[receiver] Received: d
[receiver] Stopped.
[main] stoped

相關知識點

  • struch{}代表不包含任何欄位的結構體型別,也可稱為空結構體型別。在go語言中,空結構體型別是不佔用系統記憶體的,並且所有該型別的變數都擁有相同的記憶體地址。建議用於傳遞訊號的通道都以struct{}作為元素型別,除非需要傳遞更多的資訊
  • 傳送方向通道傳送的值會被複制,接收方接收到的總是該值得副本,而不是該值本身。經由通道傳遞的值最少會被複制一次,最多會被複制兩次。例如,當向一個已空的通道傳送值,且已有至少一個接收方因此等待時,該通道會繞過本身的緩衝佇列,直接把這個值複製給最早等待的那個接收方,這種情況傳遞的值只複製一次;當從一個已滿的通道接收值,且已有至少一個傳送方因此等待時,該通道會把緩衝佇列中最早進入的那個值複製給接收方,再把最早等待的傳送方要傳送的資料複製到那個值得原先位置上(通道的緩衝佇列屬於環形佇列,這樣做是沒有問題的),這種情況傳遞的值複製兩次。
  • 通道傳遞是複製傳遞的值。因此如果傳遞的是值型別,接收方對該值得修改不會影響傳送方持有的值;如果傳遞的是引用型別,則傳送方或者接收方對該物件的修改會影響雙方所持有的物件

程式碼片段二—chan引用傳遞

原始碼

package main

import(
    "fmt"
    "time"
)

var mapChan =  make(chan map[string]int,1)

func main(){
    syncChan := make(chan struct{},2)

    //用於演示接收操作
    go func(){
        for{
            if elem,ok:= <- mapChan;ok{
                elem["count"]++   //每次接收到chan裡面的map物件後,將key為count的值加1
            }else{
                break
            }
        }
        fmt.Println("[receiveder] stoped.")
        syncChan <- struct{}{}
    }()

    //用於演示傳送操作
    go func(){
        countMap := make(map[string]int)
        for i:=0;i<5;i++{
            mapChan <- countMap
            time.Sleep(time.Second)
            fmt.Println("[sender] the count map:",countMap)
        }
        fmt.Println("[sender] stop chan.")
        close(mapChan)
        fmt.Println("[sender] stoped.")
        syncChan <- struct{}{}
    }()
    fmt.Println("[main] waiting...")
    <- syncChan
    <- syncChan
    fmt.Println("[main] stoped.")
}

執行結果

[main] waiting...
[sender] the count map: map[count:1]
[sender] the count map: map[count:2]
[sender] the count map: map[count:3]
[sender] the count map: map[count:4]
[sender] the count map: map[count:5]
[sender] stop chan.
[sender] stoped.
[receiveder] stoped.
[main] stoped.

相關知識點

  • 上述程式碼通道中傳遞的是map型別,屬於引用型別,因此接收方對元素值得修改會影響到傳送方持有的值
  • 呼叫close可以關閉通道。注意:師徒向一個已經關閉的通道傳送元素值,會讓傳送操作引發執行時恐慌。因此一定要在確保安全的前提下關閉通道。
  • 無論怎麼樣都不應該在接收端關閉通道。因為在接收端通常無法判斷髮送端是否還會向該通道傳送元素值。
  • 在傳送端關閉通道不會對接收端的接收操作產生什麼影響。如果通道在被關閉時其中仍有元素值,接收端仍然可以接收,病根據接收表示式的第二個結果值判斷通道是否關閉或者是否有元素可取
  • 內建函式len和cap可用於通道之上。len表示獲取通道中當前元素的數量,cap表示獲取通道的容量。通道的長度隨著通道中擁有的元素數量而變化,而通道的容量是在初始化的時候確定的,之後不會再修改

程式碼片段三—單向通道

原始碼

package main

import "fmt"
import "time"

var strChan = make(chan string,3)

func main(){
    synChan1 := make(chan struct{},1)
    synChan2 := make(chan struct{},2)

    go receiver(strChan,synChan1,synChan2)  
    go sender(strChan,synChan1,synChan2)

    fmt.Println("[main] waiting...")
    <- synChan2
    <- synChan2
    fmt.Println("[main] stoped.")
}

//用於演示接收
func receiver(strChan <-chan string,synChan1 <-chan struct{},synChan2 chan<- struct{}){
    <-synChan1
    fmt.Println("[receiver] Received a sync signal and wait a second...")
    time.Sleep(time.Second)
    for{
        if elem,ok := <-strChan;ok{
            fmt.Println("[receiver] Received:",elem)
        }else{
            break
        }
    }
    synChan2 <- struct{}{}
}

//用於演示傳送
func sender(strChan chan<- string,synChan1 chan<- struct{},synChan2 chan<- struct{}){
    for _,elem := range []string{"a","b","c","d"}{
        fmt.Println("{sender} sent:",elem)
        strChan <- elem
        if elem=="c"{
            fmt.Println("[sender] Sent a sync signal.")
            synChan1 <- struct{}{}
        }
    }
    fmt.Println("wait 2 seconds....")
    time.Sleep(time.Second*2)
    close(strChan)
    synChan2 <- struct{}{}
}

執行結果

[main] waiting...
[sender] sent: a
[sender] sent: b
[sender] sent: c
[sender] Sent a sync signal.
[sender] sent: d
[receiver] Received a sync signal and wait a second...
[receiver] Received: a
[receiver] Received: b
[receiver] Received: c
[receiver] Received: d
[sender] wait 2 seconds....
[main] stoped.

相關知識點

  • 單向通道分為傳送通道和接收通道。需要注意的是,無論哪種單向通道,都不應該出現在變數的宣告中,否則沒有意義。單向通道一般都是用在函式引數中,它由雙向通道變換而來,表示該函式作用域內,只能對該通道進行傳送或者接收操作。
  • 在介面或者函式引數中定義單向通道,也可以在返回值中定義單向通道。函式或者方法的返回值為單向通道可以約束呼叫該函式或者方法的結果值的使用方式】

程式碼片段四—for語句與channel

原始碼

package main

import "fmt"
import "time"

var strChan = make(chan string,1)

func main(){
    synChan2 := make(chan struct{},2)

    go receiver(strChan,synChan2)  
    go sender(strChan,synChan2)

    fmt.Println("[main] waiting...")
    <- synChan2
    <- synChan2
    fmt.Println("[main] stoped.")
}

//用於演示接收
func receiver(strChan <-chan string,synChan2 chan<- struct{}){
    for elem := range strChan{
        fmt.Println("[receiver] Received:",elem)
        time.Sleep(time.Second*2)
    }
    synChan2 <- struct{}{}
}

//用於演示傳送
func sender(strChan chan<- string,synChan2 chan<- struct{}){
    for _,elem := range []string{"a","b","c","d"}{
        fmt.Println("[sender] sent:",elem)
        strChan <- elem
        time.Sleep(time.Second*1)
    }
    close(strChan)
    synChan2 <- struct{}{}
}

執行結果

[main] waiting...
[sender] sent: a
[receiver] Received: a
[sender] sent: b
[sender] sent: c
[receiver] Received: b
[sender] sent: d
[receiver] Received: c
[receiver] Received: d
[main] stoped.

相關知識點

  • 當定義chan大小為1時,容量為1,表示緩衝大小為1;當定義chan大小為0時,容量為0,表示該通道沒有緩衝區,傳送方傳送一個值後需要等待接收方接收才可以傳送下一個值
  • for語句中可以使用range子句從通道中持續不斷地接收資料。當通道還未被初始化或者通道中沒有任何元素時,for語句所在的goruntime會陷入阻塞,阻塞的具體位置在其中的range子句處。for語句會不斷地嘗試從通道中接收元素,直到該通道關閉

程式碼片段五—select語句與channel

原始碼

package main

import "fmt"

var intChan1 chan int 
var intChan2 chan int
var numbers = []int{1,2,3,4,5}

func main(){
    intChan1 = make(chan int,1)
    intChan2 = make(chan int,1)
    select{
        case intChan1<-numbers[0]: fmt.Println("1th case is selected.")
        case intChan2<-numbers[1]: fmt.Println("2th case is selected.")
        default: fmt.Println("default is selected.")
    }
}

執行結果

1th case is selected.

或者

2th case is selected.

相關知識點

  • select語句是一種僅能用於通道傳送和接收操作的專用語句。
  • 在開始執行select語句的時候,所有跟在case關鍵字右邊的傳送語句或者接收語句中的通道表示式和元素表示式都會先求值(求值的順序是從左到右、從上到下),無論它們所在的case是否有可能被選中都是會執行
  • 在執行select語句的時候,執行時系統會自上而下地判斷每個case中的傳送或接收操作是否可以立即執行。這裡的立即執行指的是當前goruntime不會因此操作而被阻塞。只要發現有一個case上的判斷是肯定的,則該case就會被選中;如果有多個case被選中,則系統會通過一個偽隨機演算法選中一個case;如果都沒有選中則執行default語句;如果沒有default語句,則當前goruntime會在select語句處阻塞,直到至少有一個case中的傳送或者接收操作可以立即執行為止
  • select語句中的case以及default語句之間位置沒有前後