### channel
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步. **goroutine奉行通过通信来共享内存,
而不是共享内存来通信**.
引用类型channel是CSP模式具体实现,用于多个goroutine通讯,其内部实现了同步,确保并发安全.
### 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
<-channe //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同事检查通道是否已关闭或者是否为空
~~~
默认情况下,channel接收和发送数据都是阻塞的,除非另一端你已经准备好,这样就使得goroutine同步变得
更加简单,而不需要显示的lock .
### 实例
~~~
//全局变量,创建一个channel
var ch = make(chan int)
//打印机属于公共资源
func printer(str string) {
for _, data := range str {
fmt.Printf("%c\n", data)
time.Sleep(time.Second)
}
}
func person1() {
printer("hello")
ch <- 1000 //给管道数据,发送
}
func person2() {
<-ch //从管道接收数据,如果通道没有数据,它就会阻塞
printer("world")
}
func main() {
//新建两个协程,代表两个人,2个人同时使用打印机
go person1()
go person2()
//不让主协程结束
for {
}
}
~~~
~~~
h
e
l
l
o
w
o
r
l
d
~~~
### 通过channel实现同步和数据交互
~~~
func main() {
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)
}
}()
str := <-ch
fmt.Println(str)
}
~~~
死锁,等到子协程执行结束后,管道里面没有人往里面写数据.channel一直是阻塞状态,没有办法往下面继续执行了.
~~~
子协程i = 0
子协程i = 1
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/Users/artisan/go/src/learn/main.go:22 +0xe6
~~~
#### 正确做法
~~~
func main() {
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)
}
~~~
~~~
子协程i = 0
子协程i = 1
子协程执行完毕
我是子协程,工作已完毕
主协程执行完毕
~~~
- 基本语法
- 申明变量
- 常量
- 数据类型
- 强制类型转换
- 获取命令行参数
- 指针
- 概述
- new函数
- 函数
- 概述
- 不定参数类型
- 有返回值
- 函数类型
- 回调函数
- 匿名函数和闭包
- 延迟调用defer
- 工程管理
- 工作区
- src,pkg和bin目录
- 复合类型
- 概述
- 数组
- 概述
- 声明并初始化
- 拷贝传值
- slice
- 概述
- 创建切片
- 切片截取
- 切片和底层数组的关系
- slice常用方法
- 切片做函数参数
- map
- 概述
- map操作
- 结构体
- 概述
- 结构体初始化
- 结构体比较
- 结构体作为函数参数
- 结构体前加&
- 面向对象
- 概述
- 匿名组合
- 方法
- 值语义和引用语义
- 方法集
- 方法的继承
- 方法重写
- 方法值
- 接口
- 接口定义和实现
- 多态的表现
- 接口继承
- 接口转换
- 空接口
- 类型断言
- 异常处理
- error接口
- panic
- recover
- 文本文件处理
- 字符串操作
- 正则表达式
- json处理
- 文件操作
- 标准设备文件操作
- 并发编程
- 概述
- 并发和并行
- go语言并发优势
- goroutine
- goroutine概述
- 创建goroutine
- 主协程先退出
- runtime包
- Gosched
- Goexit
- GOMAXPROCE
- channel
- 多资源竞争
- channel类型
- 无缓冲channel
- 有缓冲channel
- 关闭channel
- 单向channel
- 单向channel特性
- 定时器
- Timer
- Ticker
- select
- select作用
- 超时
- sync
- 竞争状态
- 网络编程
- 网络概述
- 网络协议
- 分层模型
- 网络分层架构
- 层与协议
- 每层协议的功能
- 链路层
- 网络层
- 传输层
- 应用层
- socket编程
- 组合和继承
- 注意事项
- 细节
- go语言实现队列
- google工程师golang
- 基础语法
- 内建容器
- 面向"对象"
- 依赖管理
- 面向接口
- 函数式编程
- 错误处理和资源管理
- 测试与性能调优
- goroutine
- channel
- golang问题集
- 断言和类型转换
- Go语言圣经
- 入门
- 程序结构
- 命名
- 声明
- 变量
- 赋值
- 类型
- 包和文件
- 作用域
- 基础数据类型
- 整数
- 浮点数
- 复数
- 布尔型
- 字符串
- 常量
- 复合数据类型
- 数组
- slice
- map
- 结构体
- json
- 文本和HTML模板
- 函数
- 函数声明
- 错误
- 函数值
- 匿名函数
- defer
- panic
- recover
- 方法
- 方法声明
- 指针对象的方法
- 封装
- 接口
- 说明
- 接口是合约
- 实现接口的条件
- 接口值
- 类型断言
- 通过类型断言询问行为
- 类型开关
- Goroutines和Channels
- 协程
- channels
- 无缓冲channel
- 串联的channel
- 有缓冲channel
- 并发的循环
- select多路复用
- 并发的退出
- 并发问题的自我思考
- 基于共享变量的并发
- 竞争条件
- 互斥锁
- 读写锁
- 内存同步
- sync.Once
- 协程和线程
- 包和工具
- 测试
- 反射
- 什么是反射
- 为什么需要反射
- reflect.Type和reflect.Value
- 通过reflect.Value修改值
- 底层编程
