go-Channel

1 概述

通道是Golang提供的一種基本類型,它可以實現在協程之間的單向通信和雙向通信、發送和接收數據、以及協程同步。

channel的本質是一個隊列,遵循先進先出原則。channel是線程安全的,在任何給定時間,一個數據被設計爲只有一個協程可以對其訪問,不會發生數據競爭,多goroutine訪問時,也不需要加鎖,這些是有編譯器在底層維護的。

2 分類

在Go中,通道有兩種主要類型:

非緩衝通道:發送操作會一直阻塞,直到有一個goroutine接收到了這個消息;

帶緩衝通道:發送操作不會被阻塞,直到通道內的消息數量超過了緩衝區的大小;

3 聲明與初始化

3.1 聲明

var chanName chan toTransDatatype
var funcChan chan func(int) error
var chanChan chan chan int

通道的數據類型爲 chan,後面的 toTransDataType 表示 想要傳遞的數據類型,如 int、float、string、interface{}、結構體、指針、func()等,甚至 chan本身。

var m map[string] chan bool

上面的代碼聲明瞭一個 m 的 map,它的key爲string類型,它的value 爲 chan bool 類型

3.2 初始化

channelWithoutBuf := make(chan int) // 無緩衝通道
channelWithBuf := make(chan int, 3) // 有緩衝通道,緩衝區size爲3,提前分配了內存

 3.3 示例

package main

import "fmt"

func main() {
	var a chan int
	fmt.Println(fmt.Sprintf("a :%+v, type: %T", a, a))
	b := make(chan int)
	fmt.Println(fmt.Sprintf("b :%+v, type: %T", b, b))
	c := make(chan int, 3)
	fmt.Println(fmt.Sprintf("c :%+v, type: %T", c, c))

	a = b
	fmt.Println(fmt.Sprintf("new a :%+v, type: %T", a, a))

	b = c
	fmt.Println(fmt.Sprintf("new b :%+v, type: %T", b, b))
	return
}

輸出

a :<nil>, type: chan int
b :0x14000016540, type: chan int
c :0x14000100000, type: chan int
new a :0x14000016540, type: chan int
new b :0x14000100000, type: chan int

可以看到,通過 var 聲明的通道,值爲nil,相當於是一個空管道。

把 b 賦值給 a,此時 a 也就成了 b;同時,把 b 賦值給 c,b也會變成 c,說明初始化的不同類型通道之間是可以互相轉化的。

4 操作

通道的操作符爲 <-,語法是:

  • chanName<- value:把value變量的值寫入chanName通道;
  • value := <-chanName:從chanName中取出數據,賦值給value變量;

 我們來試試:

package main

import "fmt"

func main() {
	var chanA chan int
	chanA <- 1
	data := <-chanA
	fmt.Println(fmt.Sprintf("data: %+v, type:%T", data, data))
	//close(chanA)
	//// 由管道中讀寫數據,<-操作符是與最左邊的chan優先結合的
	// 向管道中寫入一個數據,在此需要注意:向管道中寫入數據通常會導致當前協程阻塞,直到有其他協程從這個管道中讀取數據
}

輸出

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send (nil chan)]:
main.main()
        /Users/yiyunjie/go/src/Test/main.go:7 +0x28

Process finished with the exit code 2

可以看到,main協程非正常退出,所有的協程處於休眠狀態 - 死鎖!所謂死鎖,就是雙方都在等待對方釋放鎖。

在當前場景,就是兩個協程一直在等待對方釋放資源。而在這個例子中,只有一個main函數所在的協程,並沒有新的協程參與,爲什麼會死鎖呢?

原因在於 往一個無緩衝通道里寫入數據時,發送操作會一直阻塞、等待返回,直到有一個協程接收到了這個通道里的數據,阻塞狀態纔會解除。而上面的例子,是直接在當前協程接收數據,而當前協程一直在阻塞,不能繼續往下執行,所以接收數據的代碼根本就沒執行到。這樣程序就一直在等待一個無法釋放的鎖,形成了死鎖。Go的運行時系統檢測到這種情況後,會拋出這個錯誤,並終止程序的執行。

要解決這個問題,有兩種方法:將發送操作變成非阻塞的;保持發送操作爲阻塞的,用另一個協程接收數據;

4.1 非阻塞發送

我們知道,往帶緩衝通道里寫入數據是非阻塞的,直到通道內的消息數量超過了緩衝區的大小。那我們就定義一個帶緩衝通道試試:

package main

import (
	"fmt"
)

func main() {
	chanA := make(chan int, 3)
	chanA <- 1
	data := <-chanA
	fmt.Println(fmt.Sprintf("data: %+v, type: %T", data, data))
	return
}

輸出

data: 1, type: int

沒有問題,可以正常接收到數據,且類型相符。

4.2 用新協程接收非緩衝通道信息

由於往非緩衝通道發送數據是阻塞的,所以不能把接收數據的新協程寫在發送操作之後,不然當前協程一直阻塞,新協程還是無法接受到數據。所以只能把接收動作放在發送之前。

package main

import (
	"fmt"
	"time"
)

func main() {
	chanA := make(chan int)
	go func() {
		data := <-chanA
		fmt.Println(fmt.Sprintf("data: %+v, type: %T", data, data))
	}()
	chanA <- 1
	time.Sleep(10 * time.Millisecond)
	return
}

輸出

data: 1, type: int

5 非緩衝通道的阻塞(待續)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章