> 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 快捷键