> channel管道是Go語言推薦的協程之間的通信機制
> channel是一個數據類型,主要用來解決go程的同步問題以及協程之間數據共享(數據傳遞)的問題
[TOC]
### 創建channel
> 語法
```
channel := make(chan 數據類型)
```
> 例子
```
// 創建int類型的channel
channel := make(chan int)
```
### 讀取channel的數據
> 語法
~~~
// 從channel讀取數據,保存至變量v
v := <-channel
// 從channel讀取數據,數據直接丟棄
<-channel
~~~
> 如果channel沒有數據,讀取channel會阻塞,同時觸發寫channel攜程
~~~
package main
import (
"fmt"
"strconv"
"time"
)
func collectPersons(n int, name string, c chan string) {
for i := 0; i < n; i++ {
// 往channel寫數據
c <- name + strconv.FormatInt(int64(i), 10)
fmt.Println("hello" + strconv.FormatInt(int64(i), 10))
}
}
func main() {
// 定義string類型的channel,緩沖隊列大小是10
channel := make(chan string, 5)
// 創建一個協程,往channel里寫數據
go collectPersons(5, "jiaojiao", channel)
fmt.Println("___main111___")
time.Sleep(1 * time.Second)
// 第1句:讀取channel阻塞(因為channel數據為空),同時觸發collectPersons()里的channel寫數據
fmt.Println(<-channel)
fmt.Println("___main222___")
fmt.Println(<-channel)
}
~~~
> 結果
~~~
___main111___
hello0
hello1
hello2
hello3
hello4
jiaojiao0
___main222___
jiaojiao1
___main333___
jiaojiao2
~~~
> 如果channel里根本就沒有寫入數據,channel讀取數據時,則會超時報錯,則可以采用`select`來處理超時等待問題
~~~
package main
import (
"fmt"
)
func main() {
channel := make(chan string, 2)
channel <- "haha"
fmt.Println(<-channel)
// 第2句讀取,因為channel沒有數據,則會報錯,為了防止此類問題,需要用到select
fmt.Println(<-channel)
}
~~~
### 往channel寫數據
```
// 往channel變量c中,寫入int數據100
channel <- 100
```
### channel緩沖區
~~~
// 有緩沖區channel
ch := make(chan int, 100)
// 無緩沖區channel
ch := make(chan int) //等價于make(chan int, 0)
~~~
#### 無緩沖區channel
> 無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道
> 這種類型的通道要求`發送channel`和`接收channel`同時準備好,才能完成發送和接收操作,否則都阻塞等待
> 對通道進行`發送`和`接收`是同步的操作行為。其中任意一個操作都無法離開另一個操作單獨存在
~~~
package main
import (
"fmt"
"strconv"
)
func say(name string, c chan string) {
for i := 0; i < 5; i++ {
c <- name + strconv.FormatInt(int64(i), 10)
fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10))
}
}
func main() {
// 定義一個無緩沖區的channel
channel := make(chan string)
//channel := make(chan string, 0)
go say("jiaojiao", channel)
// 由于是無緩沖區channel,每次讀取的時候,都會去觸發寫channel,類似于同步方法
fmt.Println(<-channel)
fmt.Println(<-channel)
fmt.Println(<-channel)
fmt.Println("done")
}
~~~
> 結果
> 以下的jiaojiao和hello無所謂誰先執行,因為`無緩沖區的channel`是需要讀取和寫操作同時準備好了后,才會執行,所以print()信息時候,很有可能有先有后
~~~
_______hello0
jiaojiao0
----------------------
jiaojiao1
_______hello1
----------------------
_______hello2
jiaojiao2
----------------------
done
~~~
#### 有緩沖區channel
> 緩沖區,指的是channel中有一個緩沖隊列,只有緩沖區填滿后才會阻塞寫操作
~~~
package main
import (
"fmt"
"strconv"
)
func say(name string, c chan string) {
for i := 0; i < 5; i++ {
c <- name + strconv.FormatInt(int64(i), 10)
fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10))
}
}
func main() {
// 定義一個channel,用來數據通訊
channel := make(chan string, 5)
go say("jiaojiao", channel)
// 第一句讀取的時候,讀channel阻塞,并且觸發寫channel,由于channel緩沖區為5,所以寫操作時一次性執行了5次
fmt.Println(<-channel)
fmt.Println(<-channel)
fmt.Println(<-channel)
fmt.Println("done")
}
~~~
> 結果
~~~
_______hello0
_______hello1
_______hello2
_______hello3
_______hello4
jiaojiao0
jiaojiao1
jiaojiao2
done
~~~
### 單向channel
> channel默認是雙向的,但是我們可以顯示的指定他為單向的
~~~
var writeChannel chan<-int = ch //只能寫,不能讀
var readChannel <-chan int = ch //只能讀,不能寫
~~~
> 例子
~~~
package main
import (
"fmt"
)
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
channel := make(chan int)
//生產者,只能寫入channel
go producer(channel)
//消費者,只能讀取消費channel
consumer(channel)
}
~~~
### 關閉channel
> 只有發送者可以關閉管道,接收者不能關閉管道
~~~
package main
import (
"fmt"
)
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
}
}
}
~~~
### 遍歷channel
> 可以使用for語句循環讀取channel中的數據
~~~
package main
import (
"fmt"
"strconv"
)
func collectPersons(n int, name string, c chan string) {
for i := 0; i < n; i++ {
// 返回當前計算結果
c <- name + strconv.FormatInt(int64(i), 10)
fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10))
}
// 通過close關閉channel
close(c)
}
func main() {
// 定義string類型的channel,緩沖隊列大小是3
channel := make(chan string, 3)
// 創建一個協程,往channel里寫數據
go collectPersons(3, "jiaojiao", channel)
// 通過range關鍵詞,循環遍歷channel
// 如果channel沒有數據,就阻塞循環,直到channel中有數據
// 如果channel關閉,則退出循環
for i := range channel {
fmt.Println(i)
}
}
~~~
> 返回結果
~~~
_______hello0
_______hello1
_______hello2
jiaojiao0
jiaojiao1
jiaojiao2
~~~
### select語句
> select語句會阻塞等待多個channel,直到滿足條件后執行(如果滿足多個條件,隨機執行)
> select語句中的每個 case 語句里必須是一個 IO 操作
> select本身不帶循環,操作需要外層的for
~~~
package main
import "fmt"
// select模式,寫入channel (當然也可改為讀取channel,根據自己業務邏輯來)
func writeLoop(c, quit chan int) {
x := 1
// 開始一個死循環
for {
// 通過select等待通道c和quit,看那個有反應,就執行對應的case語句中的代碼
select {
case c <- x:
// 如果通道c寫入數據成功,執行這里的計算邏輯
x = x + 1
case <-quit:
// 如果收到通道quit的數據,就退出函數,結束計算
fmt.Println("quit")
return
}
}
}
func main() {
// 定義一個channel,用來數據通訊
channel := make(chan int)
// 定義一個channel,用來傳遞停止通知
quit := make(chan int)
// 創建一個協程,用來打印計算結果
go func() {
// 打印10個計算結果
for i := 0; i < 10; i++ {
// 循環從channel通道中讀取10次數據,每次讀取都觸發channel的select模式
fmt.Println(<-channel)
}
// 往quit通道中發送數據0,通知writeLoop函數退出,主協程就結束了
quit <- 0
}()
// 開啟channel select模式
writeLoop(channel, quit)
fmt.Println("done")
}
~~~
> 以上代碼使用的是writeLoop(),再舉一個readLoop()的例子
~~~
package main
import "fmt"
// select模式,讀取channel
func readLoop(c, quit chan int) {
// 開始一個死循環
for {
// 通過select等待通道c和quit,看那個有反應,就執行對應的case語句中的代碼
select {
case x := <-c:
// 如果通道c讀取數據成功,執行這里的計算邏輯
fmt.Println(x)
case <-quit:
// 如果收到通道quit的數據,就退出函數
fmt.Println("quit")
return
}
}
}
func main() {
// 定義一個channel,用來數據通訊
channel := make(chan int)
// 定義一個channel,用來傳遞停止通知
quit := make(chan int)
// 創建一個協程,用來寫channel數據
go func() {
for i := 0; i < 10; i++ {
// 循環從channel通道中寫10次數據,每次寫操作都會觸發channel的select模式
channel <- i
}
// 往quit通道中發送數據0,通知readLoop函數退出,主協程就結束了
quit <- 0
}()
// 開啟channel select模式
readLoop(channel, quit)
fmt.Println("done")
}
~~~
> 如果select語句有多個分支滿足條件,那么它會隨機選擇一個執行。
~~~
package main
import (
"fmt"
)
func main() {
channel := make(chan string)
go func() {
select {
// x 和 y都滿足條件,會隨機執行
case x := <-channel:
fmt.Println(x+" from x")
case y := <-channel:
fmt.Println(y+" from y")
default:
// 如果有 default 子句,沒有滿足條件情況下,會執行該語句
// 如果沒有 default 子句,select 將阻塞,直到滿足條件后執行
fmt.Println("沒有滿足條件,select語句結束")
}
}()
channel <- "haha"
for {
;
}
}
~~~
- 基礎知識
- 開發環境
- 包名規則
- 包初始化 (init)
- 基礎數據類型
- 基礎類型轉換
- 格式化輸出
- go指針
- 流程控制語句
- 函數定義
- 匿名函數
- 數組和切片
- map集合
- 結構體
- Interface接口
- 日期處理
- 數學計算
- 正則表達式
- 協程 (并發處理)
- channel
- waitgroup
- mutex (鎖機制)
- websocket
- protobuf
- Redis
- 錯誤處理
- 打包程序
- NSQ消息隊列
- 單元測試
- beego
- 安裝入門
- Gin
- 快速入門
- 路由與控制器
- 處理請求參數
- 表單驗證
- 處理響應結果
- 渲染HTML模版
- 訪問靜態文件
- Gin中間件
- Cookie處理
- Session處理
- Gin上傳文件
- swagger
- pprof性能測試
- GORM
- 入門教程
- 模型定義
- 數據庫連接
- 插入數據
- 查詢數據
- 更新數據
- 刪除數據
- 事務處理
- 關聯查詢
- 屬于 (BELONG TO)
- 一對一 (Has One)
- 一對多 (Has Many)
- 多對多 (Many to Many)
- 預加載 (Preloading)
- 錯誤處理
- 第三方常用插件
- viper 讀取配置文件
- zap 高性能日志
- Nginx代理配置
- Goland 快捷鍵