是一种数据类型。 对应一个“管道”(通道 FIFO)
channel的定义:
make (chan 在channel中传递的数据类型, 容量) 容量= 0: 无缓冲channel, 容量 > 0 :有缓冲channel
e.g.: make(chan int) 或 make (chan string , 0)
channel有两个端:
一端:写端(传入端) chan <-
另一端: 读端(传出端)<- chan
要求:读端和写端必须同时满足条件,才在shan上进行数据流动。否则,则阻塞。
var channel = make(chan int) //创建
channel <- 8 //写入
<- channel //读出
make(chan 类型,容量)
ch := make (chan string):
写端: ch <- "hehe" 。写端写数据,读端不在读。写端阻塞
读端: str := <- ch 。读端读数据, 同时写端不在写,读端阻塞。
写端: ch <- "hehe" 。写端写数据,读端不在读。阻塞
读端: str := <- ch 。读端读数据, 同时写端不在写,读端阻塞。
len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。
func main() {
ch := make(chan string) // 无缓冲channel
// len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。
fmt.Println("len(ch)=", len(ch), "cap(ch)=", cap(ch))
go func() {
for i:=0;i<2;i++ {
fmt.Println("i = ", i,"len(ch)=", len(ch), "cap(ch)=", cap(ch))
}
// 通知主go打印完毕
ch <- "子go打印完毕"
}()
str := <- ch
fmt.Println("str=", str)
}
创建: ch := make(chan int) 或 make(chan int, 0)
通道容量为0, len = 0 。 不能存储数据。
channel 应用于 两个go程中。 一个读,另一个写。
具备同步的能力。 读、写同步。(打电话)
func main() {
ch := make(chan int)
go func() {
for i:= 0; i<5;i++ {
fmt.Println("子go程, i=",i)
ch <- i // ch <- 0
}
}()
//time.Sleep(time.Second * 2)
for i:= 0; i<5;i++ {
num := <- ch
fmt.Println("主go程读:", num)
}
}
创建: ch := make(chan int, 5)
通道容量为非0。len(ch) : channel 中剩余未读取数据个数。 cap(ch): 通道的容量。
channel 应用于 两个go程中。一个读,另一个写。
缓冲区可以进行数据存储。存储至 容量上限,阻塞。 具备 异步 能力,不需同时操作channel缓冲区(发短信)
func main() {
ch := make(chan int, 5) // 存满3个元素之前,不会阻塞
fmt.Println("len=", len(ch), "cap=", cap(ch))
go func() {
for i:=0; i< 10;i++ {
ch <- i
len := len(ch)
cap := cap(ch)
fmt.Println("子go程:i", i, "len=", len, "cap=", cap)
//fmt.Println("子go程:i", i)
}
}()
//time.Sleep(time.Second * 3)
for i:=0; i< 8;i++ {
num := <- ch
fmt.Println("主go程读到:", num)
}
}
无、有缓冲
确定不再相对端发送、接收数据。关闭channel。 使用 close(ch) 关闭channel
对端可以判断 channel 是否关闭:
if num, ok := <-ch ; ok == true {
如果对端已经关闭, ok --> false . num无数据。
如果对端没有关闭, ok --> true . num保存读到的数据。
可以使用 range 替代 ok:
for num := range ch { // ch 不能替换为 <-ch
}
总结: 1. 数据不发送完,不应该关闭。
2. 已经关闭的channel, 不能再向其写数据。 报错:panic: send on closed channel
3. 写端已经关闭channel, 可以从中读取数据。
读无缓冲channel: 读到0 。 —— 说明:写端关闭。
读有缓冲channel: 如果缓冲区内有数据,先读数据。读完数据后,可以继续读。 读到 0
func main() {
ch := make(chan int, 0)
go func() {
for i:=0; i<5;i++ {
ch <- i
}
close(ch) // 写端,写完数据主动关闭channel
//ch <- 790
fmt.Println("子go 结束")
}()
time.Sleep(time.Second * 2)
for num := range ch {
fmt.Println("读到数据:", num)
}
}
默认的channel 是双向的。 var ch chan int ch = make(chan int)
单向写channel: var sendCh chan <- int sendCh = make(chan <- int) 不能读操作
单向读channel: var recvCh <- chan int recvCh = make(<-chan int)
转换:
1. 双向channel 可以 隐式转换为 任意一种单向channel
sendCh = ch
2. 单向 channel 不能转换为 双向 channel
ch = sendCh/recvCh error!!!
传参: 传【引用】
func send(out chan <- int) {
out <- 89
close(out)
}
func recv(in <- chan int) {
n := <-in
fmt.Println("读到", n)
}
func main() {
ch := make(chan int) // 双向channel
go func() {
send(ch) // 双向channel 转为 写channel
}()
recv(ch)
}
生产者: 发送数据端
消费者: 接收数据端
缓冲区: 1. 解耦 ( 降低生产者 和 消费者之间 耦合度 )
2. 并发 (生产者消费者数量不对等时,能保持正常通信)
3. 缓存 (生产者和消费者 数据处理速度不一致时, 暂存数据)
模型代码实现。---- 模拟订单代码实现。 参阅讲义
func producer(out chan<- int) {
for i:=0; i<10;i++ {
fmt.Println("生产:", i*i)
out <- i*i
}
close(out)
}
func consumer(in <- chan int) {
for num := range in {
fmt.Println("消费者拿到:",num)
time.Sleep(time.Second)
}
}
func main() {
ch := make(chan int, 6)
go producer(ch) // 子go程 生产者
consumer(ch) // 主go程 消费
}
Timer:创建定时器,指定定时时长,定时到达后。 系统会自动向定时器的成员 C 写 系统当前时间。 (对 chan 的写操作)
type Timer struct {
C <-chan Time
r runtimeTimer
}
sleep()
NewTimer
After
读取 Timer.C 得到 定时后的系统时间。并且完成一次 chan 的 读操作。
time.After() 定时:
指定定时时长,定时到达后。 系统会自动向定时器的成员 写入 系统当前时间。
返回 可读 chan 。 读取,可获得系统写入时间。
总结: Sleep、NewTimer、After —— time包
定时器的 停止、重置:
1) 创建定时器 myTimer := time.NewTimer(2 * time.Second)
2) 停止: myTimer.Stop —— 将定时器归零。 <-myTimer.C 会阻塞
3) 重置:myTimer.Reset(time.Second)
func main() {
fmt.Println("当前时间:", time.Now())
// 创建定时器
myTimer := time.NewTimer(time.Second * 2)
nowTime := <- myTimer.C // chan 类型
fmt.Println("现下时间:", nowTime)
}
// 3 种定时方法
func main() {
// 1 . sleep
time.Sleep(time.Second)
// 2. Timer.C
myTimer := time.NewTimer(time.Second * 2) // 创建定时器, 指定定时时长
nowTime := <- myTimer.C // 定时满,系统自动写入系统时间
fmt.Println("现下时间:", nowTime)
// 3 time.After
fmt.Println("当前时间:", time.Now())
nowTime2 := <-time.After(time.Second * 2)
fmt.Println("现下时间:", nowTime2)
}
// 定时器的停止和重置
func main() {
myTimer := time.NewTimer(time.Second * 10) // 创建定时器。
myTimer.Reset(1 * time.Second) // 重置定时时长为 1
go func() {
for {
<- myTimer.C
fmt.Println("子go程,定时完毕")
}
}()
//myTimer.Stop() // 设置定时器停止
for {
;
}
}
type Ticker struct {
C <-chan Time
r runtimeTimer
}
1) 创建周期定时器 myTicker := time.NewTicker(time.Second)
定时时长到达后,系统会自动向 Ticker 的 C 中写入 系统当前时间。
并且,每隔一个定时时长后,循环写入 系统当前时间。
2) 在 子 go 程中循环读取 C。获取系统写入的时间。
func main() {
quit := make(chan bool) // 创建一个判断是否 终止的channel
fmt.Println("now: ", time.Now())
myTicker := time.NewTicker(time.Second) // 定义一个周期定时器
i := 0
go func() {
for {
nowTime := <-myTicker.C
i++
fmt.Println("nowTime:", nowTime)
if i == 3 {
quit <- true // 解除 主go程阻塞。
break // return // runtime.Goexit
}
}
}()
<-quit // 子go程 循环获取 <-myTicker.C 期间,一直阻塞
}
作用: 用来监听 channel 上的数据流动方向。 读?写?
用法: 参考 switch case 语句。 但!case后面必须是IO操作,不可以任意写判别表达式。
注意事项:
1. 监听的case中,没有满足监听条件,阻塞。
2. 监听的case中,有多个满足监听条件,任选一个执行。
3. 可以使用default来处理所有case都不满足监听条件的状况。 通常不用(会产生忙轮询)
4. select 自身不带有循环机制,需借助外层 for 来循环监听
5. break 跳出 select中的一个case选项 。类似于switch中的用法。
func main() {
ch := make(chan int) // 用来进行数据通信的 channel
quit := make(chan bool) // 用来判断是否退出的 channel
//ch2 := make(chan string)
go func() { // 写数据
for i:=0; i<5;i++{
ch <- i
time.Sleep(time.Second)
}
close(ch)
quit <- true // 通知主go程 退出
runtime.Goexit()
}()
for { // 主go程 select 监听 chan数据流动
select {
case num := <-ch: // 不可读,阻塞。可以读,将数据保存至num
fmt.Println("读到:", num) // 模拟使用数据
case <-quit: // 不可读,阻塞。可以读,将主go程结束。
break // break 跳出 select 不可用
//runtime.Goexit() // 终止 主 go 程 不可用
//return // 终止进程
}
fmt.Println("============") // select 自身不带有循环机制,需借助外层 for 来循环监听
}
}
代码思路:
1 1 2 3 5 8 13 21 34 55 89
x = y
y = x+y
代码:
func fibonacci(ch <-chan int, quit <-chan bool) {
for {
select {
case num := <-ch :
fmt.Print(num , " ")
case <-quit:
//return
runtime.Goexit() //等效于 return
}
}
}
func main() {
ch := make(chan int)
quit := make(chan bool)
go fibonacci(ch, quit) // 子go 程 打印fibonacci数列
x, y := 1, 1
for i:=0; i<40;i++ {
ch <- x
x, y = y, x+y
}
quit <- true
}
select 监听 time.After() 中 channel 的读事件。 如果定时时间到,系统会向该channel中写入系统当前时间。
select {
case <-time.After(time.Second * 5)
定时到达后,要处理的内容
}
//以下有个goto的使用
func main() {
ch := make(chan int)
quit := make(chan bool)
go func() { // 子go 程获取数据
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(3 * time.Second):
quit <- true
goto lable
// return
// runtime.Goexit()
}
}
lable:
}()
for i:=0; i<2; i++ {
ch <- i
time.Sleep(time.Second * 2)
}
<-quit // 主go程,阻塞等待 子go程通知,退出
fmt.Println("finish!!!")
}