談談golang中的channel


在golang中channel用於goroutine之間的通信,在閱讀這篇文章之前,我已經默認你已經熟悉了(至少了解了)goroutine

1. channel的定義

channel是引用類型,需要實用make來創建channel,如下

make(chan Type, [buffer])

make對於channel接收兩個參數,第一個是通道的類型,第二個是個可選參數代表通道緩沖區的大小(省略代表無緩沖),比如創建一個用於傳遞int類型的通道可以如下定義:

make(chan int)	// 無緩沖
make(chan int, 2) // 帶有兩個緩沖的通道

要想使用通道必須make! 以下的種種操作都是基於已經make后的channel,不再討論由於通道沒有make而引起的低級錯誤

2. channel的操作

1. 向channel中放入元素

  • 我們可以使用<-符號指向channel來將元素放入channel中

  • 注意向通道中傳值必須要求該通道還有容量(緩沖),而且通道不能關閉

  • 對於無緩沖的或者緩沖已經滿了的channel不可以輕易的傳入值,必須要有goroutine同時在取元素才可以放入

向一個有緩沖,非滿的channel傳值

c := make(chan int, 1) // 定義一個帶有一個緩沖的通道
c <- 1				   // 向通道中傳入一個1,正常

向一個有緩沖,滿的channel傳值

c := make(chan int, 1) // 定義一個帶有一個緩沖的通道
c <- 1				   // 向通道中傳入一個值,這個值傳入后填滿了該通道
c <- 2                 // 再向通道中傳入一個值,報錯!!!!!!!!!!!!!!!!!!!!!!!!!!!

向一個無緩沖,的channel傳值

c := make(chan int) // 定義一個無緩沖通道
c <- 1              // 向無緩沖通道傳值,報錯!!!!!!!!!!!!!!!!!!!!!!!!!!!!

其實上述兩種錯誤均是由於通道滿了而引起的(無緩沖的通道可以看成是緩沖為0的通道),解決方法很簡單,只要保證有個goroutine同時在從該通道中取值即可,我這里針對向一個有緩沖,滿的channel傳值給出解決思路

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func main() {
	c := make(chan int, 1)

	c <- 1  // 因為我們定義的通道帶有一個緩沖,
	        // 所以在發布任務之前允許存在最多一個任務投遞,也可以寫在發布任務之后

	// for循環用於發布任務
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go doWork(c)	// 如果通道現在是空的 goroutine會等待,
		                // 如果檢測到現在是空的且沒有任務投遞,就會報錯
	}

	c <- 2	// 多余的任務投遞必須在任務發布之后

	wg.Wait()
}

func doWork(i chan int) {
	defer wg.Done()
	a := <-i
	fmt.Printf("%d號開始工作了\n", a)
	time.Sleep(time.Second * 2) // 模擬耗時
	fmt.Printf("%d號結束工作了,輸出:%d\n", a, a)
}

對於空channel傳值報錯的解決思路就留給讀者了

2. 從channel中取出元素

  • 我們可以使用<-符號指向變量來將channel中的元素放變量

    此時可以接收兩個值一個數值一個狀態

    v, ok := <-c   // c是通道,v是取到的值,ok是狀態,正常時是true,從關閉的空通道取值是false
    
  • 可以通過range取值

  • 注意從通道中取值必須要求該通道還有值

  • 對於無緩沖的或者緩沖已經空了的channel不可以輕易的取出值,必須要同時在放元素才可以取出

  • 可以向已經關閉的通道取值

ok為true的例子

c := make(chan int, 1)
c <- 1
a, ok := <-c
fmt.Println(a, ok)  // 輸出 1 true

ok為false的例子

c := make(chan int, 1)
close(c)
a, ok := <-c
fmt.Println(a, ok)  // 輸出 0 false

ok的應用--循環取值

for {
    v, ok := <- c
    if !ok {
        break
    }
    fmt.Println(v)
}

range取值

func main() {
	c := make(chan int, 10)
	for i:=0; i< 10; i++ {
		c <- i
	}
	close(c)

	for v := range c{
		fmt.Println(v)
	}
}

向一個有緩沖,非空的channel取值

c := make(chan int, 1) // 定義一個帶有一個緩沖的通道
c <- 1				   // 向通道中傳入一個1,使通道非空
i := <-c			   // 從通道中取出一個值賦給變量i
// 如果只是想取出值而不想對該值做任何其他操作,可以這么寫    <-c   左邊省略接收者

向一個有緩沖,空的channel取值

c := make(chan int, 1) // 定義一個帶有一個緩沖的通道
<-c                    // 向空通道中取出一個值,報錯!!!!!!!!!!!!!!!!!!!!!!!!!!!

向一個無緩沖,的channel取值

c := make(chan int) // 定義一個無緩沖通道
<-c                 // 向無緩沖通道取值,報錯!!!!!!!!!!!!!!!!!!!!!!!!!!!!

其實上述兩種錯誤均是由於通道空了而引起的(無緩沖的通道可以看成是緩沖為0的通道),解決方法很簡單,只要保證同時在從該通道中存值即可,我這里針對向一個有緩沖,空的channel取值給出解決思路

package main

import (
	"fmt"
	"sync"
	"time"
)

var wg sync.WaitGroup

func main() {
	c := make(chan int, 1)
	wg.Add(1)
	go doWork(c)
	<-c			// 取值必須在放值之后
	wg.Wait()
}

func doWork(i chan int) {
	defer wg.Done()
	fmt.Printf("goroutine開始工作了\n", )
	time.Sleep(time.Second * 2) // 模擬耗時
	i <- 1
	fmt.Printf("goroutine結束工作了,放入:%d\n", 1)
}

對於空channel取值報錯的解決思路就留給讀者了

3. 關閉通道

對於一個通道我們可以使用close內置函數來進行關閉,關閉后的通道具有以下特點

  • 向一個已經關閉的通道發送值是不允許的,會報錯
  • 從一個已經關閉但是里面還有值的通道取值是允許的,可以正常獲取到值
  • 從一個已經關閉但是為空的通道取值是允許的,會獲取通道類型元素的零值
  • 不可以再次關閉一個已經關閉的通道,會報錯
  • 已經關閉的通道無法再次打開

例子1: 向一個已經關閉的通道發送值

c := make(chan int, 1)
close(c)
c <- 1		// 報錯!!!!!!!!!!!

例子2: 從一個已經關閉但是里面還有值的通道取值

c := make(chan int, 1)
c <- 1
close(c)
a := <-c
fmt.Println(a)	// 輸出 1

例子3: 從一個已經關閉但是為空的通道取值

c := make(chan int, 1)
close(c)
a := <-c
fmt.Println(a)  // 輸出 0 

例子4: 關閉一個已經關閉的通道

c := make(chan int, 1)
close(c)
close(c) // 報錯: panic: close of closed channel

3. 單向通道

  • 在函數中使用通道時我們可以限制其為只讀通道或者只寫通道

定義只讀通道的例子

func doWork(i <-chan int) {
	<-i		 // 只能取值
	//i <- 1 // 存值操作將不被允許
}

定義只寫通道的例子

func doWork(i chan <- int) {
	i <- 1      // 只能存值
	//<-i		// 取值操作將不被允許
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM