go channel 詳細介紹
一、介紹
go 以高併發著稱,而支撐這個高併發的,goroutine 協程是主要原因之一。
goroutine 讓我們輕鬆實現異步,非阻塞。
但這種異步,也帶來的問題是執行順序的不確定性。
以及數據同步的問題。在這種情況下,go 官方引入了 channel 來通信,實現數據共享。
channel是用於goroutine的數據通信
在這張圖上面,我們在主協程,總共開了 5 個 goroutine,然後每個 goroutine,通過 ch 的 channel 通信共享內存,處理寫以及讀的數據。
1.1 語法
Channel 通過操作符 <- 執行 寫入以及讀取操作。
ch <- v // 寫入v到channel
v := <-ch // 從channel讀取數據。
(箭頭的指向就是數據的流向)
1.2 操作
通道有發送(send)、接收 (receive)和關閉(close)三種操作。
我們先定義一個 channel,因爲 channel 的空值是 nil,所以必須 make 才能使用。
ch := make(chan int)
1.2.1 發送
將一個值發送到通道中。
ch <- 10 //把10發送到 ch 中
1.2.2 接收
從一個通道中接收值。
x := <-ch //從ch中接收值,並賦值給x
<-ch //從ch中接收值,忽略結果
1.2.3 close()
我們通過調用內置的 close 函數來關閉通道。
close(ch)
1.3 channel 在 nil 的時候 panic 報錯,使用的時候要注意
1.3.1 關閉一個 nil 值 channel 會引發 panic
//main.關閉一個 nil 值 channel 會引發 panic。!
func TestNew(t *testing.T) {
var ch chan struct{}
close(ch)
}
1.3.2 關閉一個已關閉的 channel 會引發 panic
//關閉一個已關閉的 channel 會引發 panic
func TestClose(t *testing.T) {
ch := make(chan struct{})
close(ch)
close(ch)
}
1.3.3 向一個已關閉的 channel 發送數據。會引發 panic
//向一個已關閉的 channel 發送數據。會引發 panic。
func TestCloseSend(t *testing.T) {
ch := make(chan struct{})
close(ch)
ch <- struct{}{}
}
二、channel 用來同步
我們讀取一個 chanel,這個 channel 一直到有寫入的時候,才能讀取,否則會一直阻塞着。
func TestSync(t *testing.T) {
done := make(chan bool)
go worker(done)
//一直阻塞着,等待任務完成
<-done
}
func worker(done chan bool) {
time.Sleep(time.Second*10)
// 通知任務已完成
done <- true
}
這樣也有一個問題,萬一 worker 這個協程,有很長時間的話,就會一直阻塞,我們可以加一個保底的時間。
func TestSync(t *testing.T) {
done := make(chan bool)
go worker(done)
//一直阻塞着,等待任務完成
select {
case <-done:
case <-time.After(time.Second):
}
}
func worker(done chan bool) {
time.Sleep(time.Second * 10)
// 通知任務已完成
done <- true
}
我們用 select 改進了一下,讓它有一個 1 秒的打底。超過 1 秒,就可以走出來了,不用一直阻塞着。
三、多個協程,多個 channel 用 select 來監聽
如下面的代碼,
開了三個 channel(http1,http2,time.After),兩個子協程。
用 select 來監聽,哪個 channel 先返回。
import (
"math/rand"
"testing"
"time"
)
func TestSelect(t *testing.T) {
res1 := make(chan string)
res2 := make(chan string)
go http1(res1)
go http2(res2)
select {
case v1 := <-res1:
t.Log(v1)
case v2 := <-res2:
t.Log(v2)
case <-time.After(650 * time.Millisecond):
t.Log("650 Millisecond over, timeover")
}
time.Sleep(time.Second * 1)
}
func http1(res1 chan string) {
time.Sleep(500 * time.Millisecond)
res1 <- "res1 ok"
}
func http2(res3 chan string) {
//[0-1000)Millisecond 隨機
rand.Seed(time.Now().UnixNano())
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
res3 <- "res3 ok"
}
輸出:
res2 ok
或者
res1 ok
看 http2 隨機的事多少。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/7yJ9Oulv40yKEJPQj5vflg