[TOC]
# 簡介
* 讀寫類型:`chan int`
* 只讀類型:`<-chan int`,叫做receive-only
* 只寫類型:`chan<- int`,叫做send-only
channel支持3種類型(通過%T看到的)
* len是未讀取的數據
* cap是容量
* 無緩沖區,會同步起來
* 有緩沖區就像發短信
1. 對于同一個通道,發送操作之間是互斥的,接收操作之間也是互斥的。
2. 發送操作和接收操作中對元素值的處理都是不可分割的。
3. 發送操作在完全完成之前會被阻塞。接收操作也是如此
元素值從外界進入通道時會被復制。更具體地說,進入通道的并不是在接收操作符右邊的那個元素值,而是它的副本
協程運行在相同的地址空間,因此訪問共享內存必須做好同步.協程奉行**通過通信來共享內存,而不是共享內存來通信**
引用類型channel是csp模式的具體實現,用于多個協程通訊.內部實現了同步,確保并發安全
# 互斥性
發送操作包括了“復制元素值”和“放置副本到通道內部”這兩個步驟。
在這兩個步驟完全完成之前,發起這個發送操作的那句代碼會一直阻塞在那里。也就是說,在它之后的代碼不會有執行的機會,直到這句代碼的阻塞解除。
更細致地說,在通道完成發送操作之后,運行時系統會通知這句代碼所在的 goroutine,以使它去爭取繼續運行代碼的機會。
另外,接收操作通常包含了“復制通道內的元素值”“放置副本到接收方”“刪掉原值”三個步驟。
在所有這些步驟完全完成之前,發起該操作的代碼也會一直阻塞,直到該代碼所在的 goroutine 收到了運行時系統的通知并重新獲得運行機會為止。
說到這里,你可能已經感覺到,如此阻塞代碼其實就是為了實現操作的互斥和元素值的完整
# 阻塞性
先說針對緩沖通道的情況。如果通道已滿,那么對它的所有發送操作都會被阻塞,直到通道中有元素值被接收走。
這時,通道會優先通知最早因此而等待的、那個發送操作所在的 goroutine,后者會再次執行發送操作。
由于發送操作在這種情況下被阻塞后,它們所在的 goroutine 會順序地進入通道內部的發送等待隊列,所以通知的順序總是公平的。
相對的,如果通道已空,那么對它的所有接收操作都會被阻塞,直到通道中有新的元素值出現。這時,通道會通知最早等待的那個接收操作所在的 goroutine,并使它再次執行接收操作。
因此而等待的、所有接收操作所在的 goroutine,都會按照先后順序被放入通道內部的接收等待隊列。
對于非緩沖通道,情況要簡單一些。無論是發送操作還是接收操作,一開始執行就會被阻塞,直到配對的操作也開始執行,才會繼續傳遞。由此可見,非緩沖通道是在用同步的方式傳遞數據。也就是說,只有收發雙方對接上了,數據才會被傳遞。
并且,數據是直接從發送方復制到接收方的,中間并不會用非緩沖通道做中轉。相比之下,緩沖通道則在用異步的方式傳遞數據
**對于值為nil的通道,不論它的具體類型是什么,對它的發送操作和接收操作都會永久地處于阻塞狀態。它們所屬的 goroutine 中的任何代碼,都不再會被執行。**
**注意,由于通道類型是引用類型,所以它的零值就是nil。換句話說,當我們只聲明該類型的變量但沒有用make函數對它進行初始化時,該變量的值就會是nil。我們一定不要忘記初始化通道!**
# channel類型
和map類似,channel也是一個對應make創建的底層數據結構的引用
當我們復制一個channel或用于函數參數傳遞時,我們只是拷貝了一個channel的引用,因此調用者何被調用這將引用同一個channel對象.和其他的引用類型一樣,channel的零值也是nil
定義有一個channel時,需要定義發送到channel的值的類型.channel可以使用內置的make()函數來創建
~~~
make(chan Type) //等價于make(chan Type, 0)
make(chan Type, capacity)
~~~
當capacity等于0時,channel是無緩沖阻塞讀寫的,
當capacity>0時,channel有緩沖,是非阻塞的,直到寫滿capacity個元素才阻塞寫入
channel通過操作符`<-`來接收和發送數據,發送和接收數據語法:
~~~
channel <- value //發送value到channel
<- channel //接收并將其丟棄
x := <-channel //從channel中接收數據,并賦值給x
x, ok := <-channel //功能同上,同時檢查通道是否已關閉或者為空
~~~
**默認情況下,channel接收和發送數據都是阻塞的**,除非另一端已經準備好了,這樣就使得協程同步變得簡單,而不需要顯示的lock
# 解決資源爭搶
~~~
//全局變量,創建一個channel
var ch = make(chan int)
//定義一個打印機
//打印機屬于公共資源
func Printer(str string) {
for _, data := range str{
fmt.Printf("%c", data)
time.Sleep(time.Second)
}
fmt.Printf("\n")
}
//p1執行完后才到p2
func Person1() {
Printer("hello")
//給管道寫數據
ch <- 666
}
func Person2() {
//從管道取數據,通道沒數據就阻塞
<-ch
Printer("world")
}
func main() {
//新建2個協程,代表2個人,2個人同時使用打印機
go Person1()
go Person2()
//特意不讓主協程結束,死循環
for {
}
}
~~~
# 實現同步和數據交互
~~~
func main() {
//創建channel
ch := make(chan string)
defer fmt.Println("主協程也結束")
go func() {
defer fmt.Println("子協程調用完畢")
for i := 0; i < 2; i++ {
fmt.Println("子協程 i = ", i)
time.Sleep(time.Second)
}
//給主協程發送數據
ch <- "我是子協程,工作完畢"
}()
//小心會永遠阻塞
str := <-ch //沒有數據前會阻塞
fmt.Println("str = ", str)
}
~~~
# 無緩沖的channel
本身是不存放東西
無緩存的channel是指在接收前沒有能力保存任何值的通道
這種類型的通道要求發送協程和接收協程同時準備好,才能完成發送和接收操作.
如果兩個協程沒有同時準備好,通道會導致先執行發送或接收操作的協程阻塞等待
這種對通道進行發送和接收的交互行為本身就是同步的.其中任意一個操作都無法離開另一個操作單獨存在
無緩存的channel格式
~~~
make(chan Type) //等價于make(chan Type, 0)
~~~
如果沒有指定緩沖區容量,那么該通道就是同步的,因此會阻塞到發送者準備好發送和接收者準備好接收
~~~
func main() {
//創建channel,無緩存的
ch := make(chan int, 0)
//len(ch)緩沖區剩余數據個數,cap(ch)緩沖區大小
fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
//新建協程
go func() {
for i := 0; i < 3 ; i++ {
fmt.Println("子協程: ", i)
ch <- i //往channel寫東西
fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
}
}()
//延遲
time.Sleep(time.Second*2)
for i := 0; i < 3; i++ {
num := <-ch //channel沒內容前會阻塞
fmt.Println("主協程: ", num)
}
}
~~~
輸出
~~~
len(ch) = 0, cap(ch) = 0
子協程: 0
主協程: 0
len(ch) = 0, cap(ch) = 0
子協程: 1
len(ch) = 0, cap(ch) = 0
子協程: 2
主協程: 1
主協程: 2
~~~
# 有緩沖的channel
有緩存的channel格式
~~~
make(chan Type, capacity)
~~~
如果給定一個緩沖區容量,通道就是異步.只要緩沖區有沒有使用空間用于發送數據,或還包含可以接收的數據,那么其通信就會無阻塞的進行
~~~
func main() {
//創建channel
ch := make(chan int, 3)
//len(ch)緩沖區剩余數據個數,cap(ch)緩沖區大小
fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
//新建協程
go func() {
for i := 0; i < 10 ; i++ {
fmt.Println("子協程: ", i)
ch <- i //往channel寫東西
fmt.Printf("len(ch) = %d, cap(ch) = %d\n", len(ch), cap(ch))
}
}()
//延遲
time.Sleep(time.Second*2)
for i := 0; i < 3; i++ {
num := <-ch //channel沒內容前會阻塞
fmt.Println("主協程: ", num)
}
}
~~~
輸出
~~~
len(ch) = 0, cap(ch) = 3
子協程: 0
len(ch) = 1, cap(ch) = 3
子協程: 1
len(ch) = 2, cap(ch) = 3
子協程: 2
len(ch) = 3, cap(ch) = 3
子協程: 3
主協程: 0
len(ch) = 2, cap(ch) = 3
子協程: 4
len(ch) = 3, cap(ch) = 3
子協程: 5
主協程: 1
主協程: 2
len(ch) = 3, cap(ch) = 3
~~~
# 關閉channel
**只有發送者可以關閉管道,接收者不能關閉管道**
關閉channel無法發送數據,發送會報錯,可以讀取
~~~
close(channel)
~~~
寫端關閉,可以從中讀到數據0,說明寫端關閉了
判斷是否關閉channel
~~~
if num, ok := <-ch; ok ==true //false關閉
~~~
~~~
func main() {
//創建channel
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i //寫數據
}
//不需要寫數據了,關閉channel
close(ch)
}()
for {
//如果ok為true,說明管道沒關閉
if num, ok := <-ch; ok == true {
fmt.Println("num = ", num)
} else {
//管道關閉
break
}
}
}
~~~
輸出
~~~
num = 0
num = 1
num = 2
num = 3
num = 4
~~~
# range遍歷channel內容
~~~
func main() {
//創建channel
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i //寫數據
}
//不需要寫數據了,關閉channel
close(ch)
}()
for num := range ch {
fmt.Println("num: ", num)
}
}
~~~
# 單向channel
默認情況下,通道是雙向的,也就是說,既可以往里面發送數據也可以同里面接收數據
但是,我們經常看見一個通道作為參數進行傳遞而值希望對方是單向使用的,要么只讓它發送數據,要么只讓它接收數據,這時候我們可以指定通道的方向
單向channel變量的聲明非常簡單,如下
~~~
var ch1 chan int //ch1是一個正常的channel,不是單向的
var ch2 chan<-float64 //ch2是單向的channel,只用于寫float64數據,只寫
var ch3<-chan int //ch3是單向channel,只用于讀取int數據,只讀
~~~
* `chan<-`表示數據進入管道,要把數據寫進管道,對于調用者就是輸出
* `<-chan`表示數據從管道出來,對于調用者就是得到管道的數據,當然就是輸入
可以將channel隱式轉換為單向隊列,只收或只發,**不能將**單向channel轉換為普通channel:
~~~
func main() {
//創建channel
ch := make(chan int)
//雙向channel能隱式轉換為單向channel
var writeCh chan<-int = ch //只能寫,不能讀
var readCh <-chan int = ch //只能讀,不能寫
//寫
writeCh <- 666
//讀
<-readCh
//單向無法轉換為雙向
}
~~~
## 生產者和消費者
~~~
func producer(out chan<-int) {
for i := 0; i <10; i++ {
out <- i*i
}
close(out)
}
func consumer(in <-chan int) {
for num := range in{
fmt.Println("num = ", num)
}
}
func main() {
//創建channel
ch := make(chan int)
//生產者,生產數字,寫入channel
go producer(ch) //channel傳參數,引用傳遞
//消費者,從channel讀取內容,打印
consumer(ch)
}
~~~
- 基礎
- 簡介
- 主要特征
- 變量和常量
- 編碼轉換
- 數組
- byte與rune
- big
- sort接口
- 和mysql類型對應
- 函數
- 閉包
- 工作區
- 復合類型
- 指針
- 切片
- map
- 結構體
- sync.Map
- 隨機數
- 面向對象
- 匿名組合
- 方法
- 接口
- 權限
- 類型查詢
- 異常處理
- error
- panic
- recover
- 自定義錯誤
- 字符串處理
- 正則表達式
- json
- 文件操作
- os
- 文件讀寫
- 目錄
- bufio
- ioutil
- gob
- 棧幀的內存布局
- shell
- 時間處理
- time詳情
- time使用
- new和make的區別
- container
- list
- heap
- ring
- 測試
- 單元測試
- Mock依賴
- delve
- 命令
- TestMain
- path和filepath包
- log日志
- 反射
- 詳解
- plugin包
- 信號
- goto
- 協程
- 簡介
- 創建
- 協程退出
- runtime
- channel
- select
- 死鎖
- 互斥鎖
- 讀寫鎖
- 條件變量
- 嵌套
- 計算單個協程占用內存
- 執行規則
- 原子操作
- WaitGroup
- 定時器
- 對象池
- sync.once
- 網絡編程
- 分層模型
- socket
- tcp
- udp
- 服務端
- 客戶端
- 并發服務器
- Http
- 簡介
- http服務器
- http客戶端
- 爬蟲
- 平滑重啟
- context
- httptest
- 優雅中止
- web服務平滑重啟
- beego
- 安裝
- 路由器
- orm
- 單表增刪改查
- 多級表
- orm使用
- 高級查詢
- 關系查詢
- SQL查詢
- 元數據二次定義
- 控制器
- 參數解析
- 過濾器
- 數據輸出
- 表單數據驗證
- 錯誤處理
- 日志
- 模塊
- cache
- task
- 調試模塊
- config
- 部署
- 一些包
- gjson
- goredis
- collection
- sjson
- redigo
- aliyunoss
- 密碼
- 對稱加密
- 非對稱加密
- 單向散列函數
- 消息認證
- 數字簽名
- mysql優化
- 常見錯誤
- go run的錯誤
- 新手常見錯誤
- 中級錯誤
- 高級錯誤
- 常用工具
- 協程-泄露
- go env
- gometalinter代碼檢查
- go build
- go clean
- go test
- 包管理器
- go mod
- gopm
- go fmt
- pprof
- 提高編譯
- go get
- 代理
- 其他的知識
- go內存對齊
- 細節總結
- nginx路由匹配
- 一些博客
- redis為什么快
- cpu高速緩存
- 常用命令
- Go 永久阻塞的方法
- 常用技巧
- 密碼加密解密
- for 循環迭代變量
- 備注
- 垃圾回收
- 協程和纖程
- tar-gz
- 紅包算法
- 解決golang.org/x 下載失敗
- 逃逸分析
- docker
- 鏡像
- 容器
- 數據卷
- 網絡管理
- 網絡模式
- dockerfile
- docker-composer
- 微服務
- protoBuf
- GRPC
- tls
- consul
- micro
- crontab
- shell調用
- gorhill/cronexpr
- raft
- go操作etcd
- mongodb