通道是用來傳遞資料的一個資料結構,從設計上確保,在同一時刻只有一個 goroutine 能從中接識訓放入資料,發送和接收都是原子操作,不會中斷,
Go語言中的通道(channel)是一種特殊的型別,在任何時候,同時只能有一個 goroutine 訪問通道進行發送和獲取資料,goroutine 間通過通道就可以通信,
通道可用于兩個 goroutine 之間通過傳遞一個指定型別的值來同步運行和通訊,運算子 <- 用于指定通道的方向,發送或接收,如果未指定方向,則為雙向通道,
ch <- v // 把 v 發送到通道 ch
v := <-ch // 從 ch 接收資料,并把值賦給 v
宣告通道型別
通道本身需要一個型別進行修飾,就像切片型別需要標識元素型別,通道的元素型別就是在其內部傳輸的資料型別,宣告如下:
var 通道變數 chan 通道型別
chan 型別的空值是 nil,宣告后需要配合 make 后才能使用,
創建通道
宣告一個通道,使用chan關鍵字即可,通道在使用前必須先創建,通道是參考型別,需要使用 make 進行創建:
通道實體 := make(chan 資料型別)
- 資料型別:通道內傳輸的元素型別,
- 通道實體:通過make創建的通道句柄,
示例:
ch1 := make(chan int) // 創建一個整型型別的通道
ch2 := make(chan interface{}) // 創建一個空介面型別的通道, 可以存放任意格式
type Equip struct{ /* 欄位 */ }
ch2 := make(chan *Equip) // 創建Equip指標型別的通道, 可以存放*Equip
goroutine
Go 語言支持并發,我們只需要通過 go 關鍵字來開啟 goroutine 即可,goroutine 是輕量級執行緒,goroutine 的調度是由 Golang 運行時進行管理的,
Go 允許使用 go 陳述句開啟一個新的運行期執行緒, 即 goroutine,以一個不同的、新創建的 goroutine 來執行一個函式, 同一個程式中的所有 goroutine 共享同一個地址空間,
例如:
go a(x, y, z)
這樣就開啟了一個新的 goroutine,
示例:
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
sync.waitGroup
等待所有goroutine執行完成,并且阻塞主執行緒的執行,直到所有的goroutine執行完成,
GOMAXPROCS
呼叫 runtime.GOMAXPROCS() 用來設定可以并行計算的CPU核數的最大值,并回傳之前的值,
通道
如果說 goroutine 是 Go語言程式的并發體的話,那么 channels 就是它們之間的通信機制,一個 channels 是一個通信機制,它可以讓一個 goroutine 通過它給另一個 goroutine 發送值資訊,每個 channel 都有一個特殊的型別,也就是 channels 可發送資料的型別,一個可以發送 int 型別資料的 channel 一般寫為 chan int,
Go語言提倡使用通信的方法代替共享記憶體,當一個資源需要在 goroutine 之間共享時,通道在 goroutine 之間架起了一個管道,并提供了確保同步交換資料的機制,
宣告通道時,需要指定將要被共享的資料的型別,可以通過通道共享內置型別、命名型別、結構型別和參考型別的值或者指標,
這里通信的方法就是使用通道(channel),如下圖所示:

在地鐵站、食堂、洗手間等公共場所人很多的情況下,大家養成了排隊的習慣,目的也是避免擁擠、插隊導致的低效的資源使用和交換程序,
代碼與資料也是如此,多個 goroutine 為了爭搶資料,勢必造成執行的低效率,使用佇列的方式是最高效的,channel 就是一種佇列一樣的結構,
通道用途
- 使用通道來同步goroutine
- 使用異步管道來保護臨界資源(令牌 誰拿到了才能用,使用完退回管道)
- 用管道來發送事件
同步通道
緩沖長度為0的channel稱為同步管道,可以用來同步兩個routine
- 發送操作被阻塞,直到接收端準備好接收
- 接收操作被阻塞,直到發送端準備好發送
異步通道
緩沖長度大于0的channel稱為異步管道,異步 channel,就是給 channel 設定個 buffer 值
- 在 buffer 未填滿的情況下 ,不阻塞發送操作,
- 在buffer 未讀完前,不阻塞接收操作,
通過 range 關鍵字來實作遍歷讀取到的資料,類似于與陣列或切片,格式如下:
v, ok := <-ch
如果通道接收不到資料后 ok 就為 false,這時通道就可以使用 close() 函式來關閉,
示例:
// 宣告通道
ch := make(chan int)
var ch1 chan int // ch1是一個正常的channel,不是單向的
var ch2 chan<- float64 // ch2是單向channel,只用于寫float64資料
var ch3 <-chan int // ch3是單向channel,只用于讀取int資料
// 通道可以設定緩沖區,通過 make 的第二個引數指定緩沖區大小
ch := make(chan int, 2)
// 因為 ch 是帶緩沖的通道,我們可以同時發送兩個資料
// 而不用立刻需要去同步讀取資料
ch <- 1
ch <- 2
// 獲取這兩個資料
fmt.Println(<-ch)
fmt.Println(<-ch)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函式遍歷每個從通道接收到的資料,因為 c 在發送完10個資料之后就關閉了通道,
// 所以這里我們 range 函式在接收到 10 個資料之后就結束了,
// 如果上面的 c 通道不關閉,那么 range 函式就不會結束,從而在接收第 11 個資料的時候就阻塞了,
for i := range c {
fmt.Println(i)
}
}
執行結果為:
0
1
1
2
3
5
8
13
21
34
通道緩沖區
默認情況下,通道是不帶緩沖區的,發送端發送資料,同時必須有接收端相應的接收資料,
通道可以設定緩沖區,通過 make 的第二個引數指定緩沖區大小:
ch := make(chan int, 100)
帶緩沖區的通道允許發送端的資料發送和接收端的資料獲取處于異步狀態,就是說發送端發送的資料可以放在緩沖區里面,可以等待接收端去獲取資料,而不是立刻需要接收端去獲取資料,
不過由于緩沖區的大小是有限的,所以還是必須有接收端來接收資料的,否則緩沖區一滿,資料發送端就無法再發送資料了,
注意:
如果通道不帶緩沖,發送方會阻塞直到接收方從通道中接收了值,如果通道帶緩沖,發送方則會阻塞直到發送的值被拷貝到緩沖區內;
如果緩沖區已滿,則意味著需要等待直到某個接收方獲取到一個值,接收方在有值可以接收之前會一直阻塞,
示例:
package main
import "fmt"
func main() {
//定義了一個可以存盤整數型別的帶緩沖通道
// 緩沖區大小為2
ch := make(chan int, 2)
// 因為 ch 是帶緩沖的通道,我們可以同時發送兩個資料
// 而不用立刻需要去同步讀取資料
ch <- 1
ch <- 2
// 獲取這兩個資料
fmt.Println(<-ch)
fmt.Println(<-ch)
}
執行結果為:
1
2
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/132273.html
標籤:其他
