目录
写这篇文章主要参考另外一篇文章,那篇文章已经写的非常完美,我会在参考文章中写道,但里面涉及到一些基础知识,比如说,什么是channel,什么是有缓冲的channel,什么是select,如果没有这些基础知识的话,还需要到处找资料,所以计划把一些基础的go知识融合进去。
1.1 channel 基本概念相关
1.1.1 channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
1.1.2 有缓冲 channel
它的主要作用是可以实现异步,因为无缓冲的channel,实际是只有一个位置来存储数据结构,那么当它发送一个数据到channel的话,再往 channel中发送数据,程序就会阻塞掉,特别是,如果没有开goroutine去channel中取数据,你再发送数据,程序处于死锁状态,就会报错了,但是如果有缓冲,比如缓冲是2,就可以同时发送两条数据到channel,而不至于阻塞掉
可以通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
如下小例子
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
执行结果
1
2
注意到,如果不开缓冲,程序直接会死锁报错
1.2 channel 应用
channel
有点类似于管道,它在goroutine
同步与通信中,有着起承转合的作用
package main
func main() {
senderOnly := make(chan<- int) // 只能用来发送(管道的入口,只进不出)
receiverOnly := make(<-chan int) // 只能用来接收(管道的出口,只出不进)
unbuffer := make(chan int) // 无缓冲可收发
buffer := make(chan int, 2) // 有缓冲可收发
println(senderOnly, receiverOnly, unbuffer, buffer)
}
目前不知道前两种管道怎么用,因为不理解,如果只能发送,发送完了,也不能拿,哪种场景会用啊
下面介绍一些channel的使用场景:
1.2.1 等待goroutine完成
package main
func main() {
println("start main")
ch := make(chan bool)
go func() {
println("come into goroutine")
ch <- true
}()
println("do something else")
<-ch
close(ch)
println("end main")
}
运行结果:
(base) desktop-6rkqqec:study songbw$ go run test.go
start main
do something else
come into goroutine
end main
1.2.2 多个goroutine协同
三个功能不相关的goroutine
最后结果要汇总累加到result
上
package main
func main() {
println("start main")
ch := make(chan int)
var result int
go func() {
println("come into goroutine1")
var r int
for i := 1; i <= 10; i++ {
r += i
}
ch <- r
}()
go func() {
println("come into goroutine2")
var r int = 1
for i := 1; i <= 10; i++ {
r *= i
}
ch <- r
}()
go func() {
println("come into goroutine3")
ch <- 11
}()
for i := 0; i < 3; i++ {
result += <-ch
}
close(ch)
println("result is:", result)
println("end main")
}
运行结果:
(base) desktop-6rkqqec:study songbw$ go run test.go
start main
come into goroutine3
come into goroutine1
come into goroutine2
result is: 3628866
end main
1.2.3 channel 和 range
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for v := range c {
fmt.Println("out:", time.Now())
fmt.Println(v)
}
}
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i :=0; i < n; i++ {
c <- x
fmt.Println("in:",time.Now())
time.Sleep(100)
x, y = y, x+y
}
close(c)
}
执行结果:
(base) desktop-6rkqqec:study songbw$ go run channelWithRange.go
in: 2020-03-26 10:29:49.164717 +0800 CST m=+0.000076459
out: 2020-03-26 10:29:49.164727 +0800 CST m=+0.000087213
in: 2020-03-26 10:29:49.164949 +0800 CST m=+0.000308986
0
in: 2020-03-26 10:29:49.164957 +0800 CST m=+0.000316599
in: 2020-03-26 10:29:49.164962 +0800 CST m=+0.000322119
out: 2020-03-26 10:29:49.164957 +0800 CST m=+0.000316850
1
out: 2020-03-26 10:29:49.164969 +0800 CST m=+0.000329174
1
out: 2020-03-26 10:29:49.164973 +0800 CST m=+0.000332509
2
out: 2020-03-26 10:29:49.164976 +0800 CST m=+0.000335379
3
in: 2020-03-26 10:29:49.164965 +0800 CST m=+0.000324719
in: 2020-03-26 10:29:49.165008 +0800 CST m=+0.000367753
in: 2020-03-26 10:29:49.165011 +0800 CST m=+0.000371055
in: 2020-03-26 10:29:49.165013 +0800 CST m=+0.000373340
in: 2020-03-26 10:29:49.165016 +0800 CST m=+0.000375470
in: 2020-03-26 10:29:49.165018 +0800 CST m=+0.000377607
out: 2020-03-26 10:29:49.165013 +0800 CST m=+0.000373113
5
out: 2020-03-26 10:29:49.16505 +0800 CST m=+0.000409584
8
out: 2020-03-26 10:29:49.165053 +0800 CST m=+0.000413031
13
out: 2020-03-26 10:29:49.165056 +0800 CST m=+0.000415929
21
out: 2020-03-26 10:29:49.165059 +0800 CST m=+0.000418730
34
关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入
1.2.4 channel 与 select
在介绍这个例子之前,先介绍一点基础的知识
a、select 相关
类似于switch关键字,只不过它的操作都是和channel有关的,知道两点即可:
1)当有多个case同时满足条件,系统会随机选择一个 case下的语句执行
2)但是所有的 case 都会执行
b、time.After(duration)接口
1)、此接口不会阻塞,它会立即返回一个只读的时间类型的<-channel
2)、经过 duration时间后,会往管道中塞一个当前时间
package main
import "time"
func main() {
println("start main")
cond1 := make(chan int)
cond2 := make(chan uint64)
go func() {
for i := 0; ; i++ {
cond1 <- i
}
}()
go func() {
var i uint64
for ; ; i++ {
cond2 <- i
}
}()
endCond := false
for endCond != true {
select {
case a := <-cond1:
if a > 99 {
println("end with cond1")
endCond = true
}
case b := <-cond2:
if b == 100 {
println("end with cond2")
endCond = true
}
case <-time.After(time.Microsecond):
println("end with timeout")
endCond = true
}
}
println("end main")
}
执行结果,可能是
(base) localhost:study songbw$ go run test.go
start main
end with cond1
end main
也可能是
(base) localhost:study songbw$ go run test.go
start main
end with cond2
end main
还可能是
(base) localhost:study songbw$ go run test.go
start main
end with timeout
end main
我是运行了3次,这三种结果都出现了,从侧面也反映了,每次 select,case后面的语句都会执行,而刚好100次循环大致需要1ms的时间,所以三个 case 几乎同时满足条件,所以系统就随机执行,某一个case 后面的语句
文章 https://www.cnblogs.com/tobycnblogs/p/9935465.html 感觉是 channel的高级篇章,等用到了再看吧
参考文章