第六章 Channel
6.1 channel的定义
channel是内置的一种数据类型,用于两个go程中间数据的传输。
- channel关键字为
chan
,channel是引用类型,引用类型的变量需要手动分配内存空间,所以需要用make
为其创建内存空间,channel初始化的格式如下:
//Type为传输两个gotoutine之间数据的类型
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity) //capacity决定了当前的channel是无缓冲的channel还是有缓冲的channel
- 向channel中传输数据的格式:
//1. 向channel中传输数据
channel <- value //发送value到channel
//2. 向channel中读取数据
<- channel //从channel中接收数据并将其丢弃
x := <- channel //从channel中接收数据,将其赋值给x
x, ok := channel //从channel中接收数据,将其赋值给x,同时检查通道是否已关闭或者是否为空
6.2 channel的使用
下面通过将子go程中的数据传输给main go程的无缓冲channel例子来说明channel的用法:
package main
import "fmt"
func main() {
//定义一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 正在运行...")
c <- 666 //将“666”发送给c
}()
//如果没有“=”左边的“num :=”只有单独的“<- c”,则意为获取c中的数据但并没有使用
num := <-c //从c中接收数据并赋值给num
fmt.Println("num = ", num)
fmt.Println("main goroutine 结束...")
}
控制台输出:
goroutine 正在运行...
goroutine 结束
num = 666
main goroutine 结束...
6.2.1 channel具有同步两个goroutine的能力
当main程和sub程同时执行时,无法保证这两程的执行速度谁快谁慢,下面单独从无缓冲channel来分两种情况讨论,结合上面例子的解释如下:
- 当main程快时,其先执行到
num := <-c
时,sub程还没有执行到c <- 666
,此时就无法将c中的值读取出来,那么main程就会发生阻塞,后续当sub程执行到c <- 666
后,这个阻塞就会消失,管道c中的数据就顺利传输了。 - 当sub程快时,其实也是一个道理,sub程先执行到
c <- 666
,此时main程还没有执行到num := <-c
,c中的666就没有变量来接收(一个go程发出数据没有人接收是会造成死锁的),那么sub程就会发生阻塞,后续当main程执行到num := <-c
后,阻塞就会消失,管道c中的数据就顺利传输了。
所以channel将两个go程进行了同步。
6.3 无缓冲和有缓冲的channel
- 使用无缓冲的通道在goroutine之间同步(此外6.2.1中的channel就是无缓存的channel):
文字描述如下:
① 两个goroutine都到达通道,但哪个都没有开始执行发送或者接收。
② 左侧goroutine将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个goroutine会在通道中被锁住,知道交换完成。
③ 右侧的 goroutine将它的手放入通道,这模拟了从通道里接收数据。这个goroutine—样也会在通道中被锁住,直到交换完成。
④ 到 ⑤ 两个goroutine进行交换数据。
⑥ 两个goroutine都将它们的手从通道里拿出来,这模拟了被锁住的goroutine得到释放。两个goroutin现在都可以去做其他事情了。
- 使用有缓冲的通道(buffered channel)在goroutine之间同步:
文字描述如下:
① 右侧的 goroutine正在从通道接收一个值。
② 右侧的这个goroutine独立完成了接收值的动作,而左侧的goroutine正在发送一个新值到通道里。
③ 左侧的goroutine还在向通道发送新值,而右侧的goroutine正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
④ 所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。
只有当channel已经满,再向里面写数据或者当channel为空,从里面取数据才会发生阻塞。结合上图可以这样解释:只有当③中左侧goroutine将数据塞满了通道或者④中右侧goroutine从通道中没有数据可取,才会发生阻塞。
下面通过例子来呈现有缓冲channel:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) //创建带有3容量的channel
go func() {
defer fmt.Println("子go程结束...")
for i := 0; i < 3; i++ {
c <- i
fmt.Println("len(c)=", len(c), "cap(c)=", cap(c))
}
}()
time.Sleep(2 * time.Second) //因为无法保证子go程和主go程谁先执行完谁后执行完,所以让程序睡2s保证子go程能将数据能充分填满管道,保证子go程先于main go程执行完
for i := 0; i < 3; i++ {
num := <-c
fmt.Println("num=", num)
}
fmt.Println("main go程结束...")
}
控制台输出:
len(c)= 1 cap(c)= 3
len(c)= 2 cap(c)= 3
len(c)= 3 cap(c)= 3
子go程结束...
num= 0
num= 1
num= 2
main go程结束...
6.4 关闭channel的函数close()
close()
可以关闭一个channel。
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//close可以关闭一个channel
//这里必须要关闭,因为主go程中一直在无限获取channel中的数据,而子go程只传递了5个数据给channel,若不手动关闭则会造成主go程的阻塞
close(c)
}()
//若channel里有数据即ok为true,则打印data,否则终止循环
for {
if data, ok := <-c; ok { //ok为true则channel为开放状态,为false则为关闭状态
fmt.Println(data)
} else {
break
}
}
fmt.Println("Main finished...")
}
控制台输出:
0
1
2
3
4
Main finished...
关闭channel需要注意的点:
- channel不像文件一样需要经常去关闭,只有当你确实没有任何发送的数据了,或者你想显式地结束range循环之类,才去关闭channel;
- 关闭channel后,无法向channel再发送数据(引发panic错误后导致接收立即返回零值);
- 关闭channel后,可以继续从channel接收数据,比如向channel发送了几个数据,而channel中有缓存,所以可以继续接收里面的数据;
- 对于nil channel(就是只用var声明了没用make给内存空间的channel),无论收发都会被阻塞。
6.5 range在channel中的使用
6.4中主go程中无限的对channel中数据的获取可以用range进行简化:
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
//对c中的数据进行遍历赋值给data
for data := range c {
fmt.Println(data)
}
fmt.Println("Main finished...")
}
控制台输出:
0
1
2
3
4
Main finished...
6.6 select在channel中的使用
单流程下一个go只能监控一个channel的状态,select可以完成监控多个channel的状态。
select {
case <- chan1:
//如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
//如果成功向chan2写入数据,则进行该case处理语句
default:
//如果上面都没有成功,则进入default处理流程
}
以上的select监控了chan1是否可读,chan2是否可写,这两种情况谁先触发就会去处理相应的case。一般来说会在select外面包一层for来不断的对各个channel状态进行监测。
斐波那契数列例子说明select的用法:
package main
import "fmt"
func fibonacii(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x: //如果c可写,即c没有阻塞,则case就会进来
x = y
y = x + y
case <-quit: //如果quit可读,则打印quit,表示结束
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 6; i++ {
fmt.Println(<-c)
}
quit <- 0 //当循环结束给quit信道传入一个0值
}()
fibonacii(c, quit)
}
控制台输出:
1
1
2
4
8
16
quit
标签:goroutine,go,Channel,func,第六章,main,channel,通道
From: https://www.cnblogs.com/SpriteLee/p/16640993.html