go channel學習筆記

ramsey發表於2021-08-08

什麼是channel

go channel 是go語言一種核心資料型別,可以理解是一種管道,併發核心單元可以透過channel傳送或接收資料進行通訊,實現併發同步。

channel 型別

channel型別是可以有方向的,假設T是一種型別:

  1. chan T是雙向channel型別,編譯器允許對雙向channel同時進行傳送和接收。
  2. chan<- T是隻寫channel型別,編譯器只允許往channel裡面傳送資料。
  3. <-chan T是隻讀channel型別,編輯器只允許從channel裡面接收資料。
    在go語言中,透過chan建立通道,建立語法:
    channel1 := make(chan int)  //雙向channel,可讀寫
    channel2 := make(chan<- int)  //單向只寫channel
    channel3 := make(<-chan int)  //單向只讀channel
    channel_name 表示宣告的管道名,Type表示管道傳輸的資料型別。管道支援資料型別包括:int,string,struct等。

channel 分類

channel分無緩衝型別和帶緩衝型別:

  • 無緩衝型別channel
    一個執行緒向這個channel傳送了訊息後,會阻塞當前的這個執行緒,直到其他執行緒去接收這個channel的訊息。建立語法:
    intchan := make(chan int)
  • 緩衝型別channel
    帶緩衝的channel,是可以指定緩衝的訊息數量,當訊息數量小於指定值時,不會出現阻塞,超過之後才會阻塞,需要等待其他執行緒去接收channel處理。建立語法:
    intchan := make(chan int,3) 

channel的一些操作

假設ch是一個通道。

  • close(關閉一個通道)
    close(ch)
    注意:如果ch是一個nil通道,或者ch已經被關閉,上述close(ch)會觸發panic
  • 向通道ch傳送一個值
    ch<-c
    注意:ch只能是雙向通道或者是單向只寫通道。
  • 從通道ch接收一個值
    c <-ch
    注意:通道ch只能是雙向通道或者是單向只讀通道
  • 獲取通道容量
    cap(ch)
  • 獲取通道長度
    len(ch)
    注意:len函式返回是當前通道元素個數,cap函式返回的是當前通道總共能存放元素個數。

一些channel的使用例子

  1. 非緩衝通道實現資料同步
package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    done := make(chan string)
    go func(ch chan<- int, x int) {
        time.Sleep(time.Second * 5)
        ch <- x * x * x
    }(c, 10)
    go func(ch <-chan int) {
        data := <-ch
        fmt.Println(data)
        s := "程式結束"
        done <- s
    }(c)
    fmt.Println("程式開始")
    fmt.Println(<-done)
}

結果輸出:

程式開始
1000
程式結束
  1. 非緩衝通道實現超時控制
package main

import (
    "fmt"
    "time"
)

func main() {
    select {
    case <-dowork():
        fmt.Println("任務在規定時間內完成!")
    case <-time.After(time.Second * 2):
        fmt.Println("任務超時了!!!")
    }
}

func dowork() <-chan int {
    ch := make(chan int)
    go func() {
        fmt.Println("開始工作")
        time.Sleep(time.Second * 3)
        ch <- 0
    }()
    return ch
}

結果輸出:

開始工作
任務超時了!!!
  1. 帶緩衝通道實現生產者-消費者模型
package main

import (
    "fmt"
    "time"
)

func producer(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}

func consumer(ch chan int, done chan string) {
    for i := 0; i < 5; i++ {
        go func(id int) {
            for {
                data, ok := <-ch
                if !ok {
                    done <- "done"
                    return
                } else {
                    fmt.Printf("消費者 %v,完成任務 %v\n", id, data)
                    time.Sleep(time.Second * 2)
                }
            }
        }(i)

    }
}

func main() {
    done := make(chan string)
    ch := make(chan int, 10)
    go producer(ch)
    go consumer(ch, done)
    <-done
    fmt.Println("任務完成")
}

結果輸出:

消費者 4,完成任務 0
消費者 1,完成任務 1
消費者 2,完成任務 2
消費者 3,完成任務 3
消費者 0,完成任務 4
消費者 0,完成任務 5
消費者 3,完成任務 8
消費者 2,完成任務 6
消費者 1,完成任務 9
消費者 4,完成任務 7
任務完成
  1. 帶緩衝通道實現併發數量控制
package main

import (
    "fmt"
    "time"
)

func handleEvent(done chan string, task chan bool) {
    for i := 0; i < 10; i++ {
        task <- true
        go func(id int) {
            fmt.Printf("處理事件 %v\n", id)
            time.Sleep(time.Second * 1)
            <-task
            if id == 9 {
                done <- "done"
            }
        }(i)
    }

}

func main() {
    done := make(chan string)
    task := make(chan bool, 2) //並法數控制為2
    go handleEvent(done, task)
    <-done
    fmt.Println("任務完成")
}

結果輸出:

處理事件 1
處理事件 0
處理事件 2
處理事件 3
處理事件 5
處理事件 4
處理事件 7
處理事件 6
處理事件 8
處理事件 9
任務完成

channel使用注意事項

  1. 關閉一個nil通道或者一個已經關閉的通道將產生一個panic。
  2. 向一個已關閉的通道傳送資料也將導致panic。
  3. 向一個nil通道傳送資料或者從一個nil通道接收資料將使當前協程永久阻塞。
本作品採用《CC 協議》,轉載必須註明作者和本文連結
拉姆塞

相關文章