1.channel
1.1函数与函数之间如何进行通讯?
1.通过共享内存实现函数之间的通讯
共享内存实现函数间的通讯有个极大的弊端,不同的`goroutine`容易发生竞态问题(数据不安全),未来保证数据交换的正确性,必须要给数据进行加锁,这样就会造成性能问题。
2.通过通信共享内存
在Go语言中就是利用`channel` 实现两个`goroutine`之间通过通信共享内存
在Go语言中goroutine
是并发的执行体,channel
就是执行体之间的连接。
channel
可以让一个goroutine
发送特定的值到另外一个goroutine
的通讯机制。
Go语言中的通道channel
是一种特殊的类型,我们可以把这个通道看做成队列,遵循先进先出的规则。
这里要特别说明:每个管道都是一个具体类型的导管,在声明channel
的时候需要为其指定元素的类型
1.2 channel
类型
channel
是一种引用类型,(必要要进行make
函数初始化后才能使用)
channel
声明格式
var 变量 chan 元素类型
举几个栗子
var cha1 chan int64
var cha2 chan bool
var cha3 chan []int
1.3 创建channel
chanel
通道是一个引用类型,空值为nil
var cha1 chan int
fmt.Println(cha)
** 声明通道后,必须使用make
函数初始化后才能使用**
make(chan 元素类型,缓冲大小)
举几个栗子
cha1 := make(chan int)
cha2 := make(chan bool)
cha3 := make(chan []int)
1.4 channel
的操作
发送(send)、接收(receive)、关闭(close)
发送与接收使用的唯一
符号<-
我们先定义一个发送int类型的通道
cha1 :&#61; make(chan int)
发送&#xff1a;将一个值发送到通道中
cha1 <- 10
接收&#xff1a;变量接收通道的值
n :&#61; <- cha1
<- cha1
关闭&#xff1a;通过内置函数close()
来关闭通道
close(cha1)
关闭通道需要注意&#xff0c;只有在通知接收方goroutine所有的数据都发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的&#xff0c;它和关闭文件是不一样的&#xff0c;在结束操作之后关闭文件是必须要做的&#xff0c;但关闭通道不是必须的。
关闭后的通道有以下特点&#xff1a;
1.对一个已经关闭的通道发送值会导致panic
2.对一个已经关闭的通道进行接收会一直获取通道里面的值直到通道为空。
3.对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
4.关闭一个已经关闭的通道会导致panic。
1.5 无缓冲的通道
无缓冲的通道又名阻塞通道
为什么叫做阻塞通道呢&#xff1f;
package mainvar cha1 &#61; make(chan int64)
func main(){cha1 <- 10
}
编译通道在执行的时候报错
atal error: all goroutines are asleep - deadlock!goroutine 1 [chan send]:
main.main()G:/go/go_pro/src/github.com/day08/channel/main.go:5 &#43;0x45
exit status 2
这就是无缓冲通道又叫做阻塞通道的原因
我们创建了一个无缓冲通道var cha1 &#61; make(chan int64)
,无缓冲通道&#xff08;阻塞通道&#xff09;只有在有人接收值的时候才能发送值。
举个通俗一点的栗子
无缓冲通道就像你住的小区没有快递柜与代收点&#xff0c;快递员必须将这个东西送到你的手里。
无缓冲通道必须有接收值才能发送
package mainimport ("fmt""sync"
)var cha1 &#61; make(chan int64)
var wg sync.WaitGroupfunc recv1(c chan int64) {defer wg.Done()n :&#61; <-cfmt.Println("接收成功", n)
}
func main() {wg.Add(1)go recv1(cha1)cha1 <- 10fmt.Println("已经将值发送给通道")wg.Wait()
}
1.6 有缓冲通道
举个栗子
package main
func main() {cha1 :&#61; make(chan int64, 1)cha1 <- 10
}
没有报错
1.7 for range
从通道中循环取值
当向通道发送完数据后&#xff0c;使用close()
关闭通道&#xff0c;使用for range
对通道进行循环取值。
当通道被关闭时&#xff0c;在向通道中进行发送值会引发panic
,从通道取值的操作会先取完通道中的值&#xff0c;然后取到的是对应类型的零值。
举个栗子
package mainimport ("fmt""sync"
)var wg sync.WaitGroupfunc main() {cha1 :&#61; make(chan int)cha2 :&#61; make(chan int)go func() {defer wg.Done()for i :&#61; 0; i < 100; i&#43;&#43; {cha1 <- i}close(cha1)}()go func() {defer wg.Done()for {i, ok :&#61; <-cha1if !ok {break}cha2 <- i * i}close(cha2)}()go func() {defer wg.Done()for i :&#61; range cha2 {fmt.Println(i)}}()wg.Add(3)fmt.Println("main函数")wg.Wait()
}
从上面的例子中我们看到有两种方式在接收值的时候判断该通道是否被关闭&#xff0c;不过我们通常使用的是for range的方式。使用for range遍历通道&#xff0c;当通道被关闭的时候就会退出for range。
1.8 单向通道
有的时候我们会将通道作为参数在多个任务函数间传递&#xff0c;很多时候我们在不同的任务函数中使用通道都会对其进行限制。
比如限制通道在函数中只能发送或只能接收。
Go语言中提供了单向通道来处理这种情况&#xff1a;
func counter(out chan<- int) {for i :&#61; 0; i < 100; i&#43;&#43; {out <- i}close(out)
}func squarer(out chan<- int, in <-chan int) {for i :&#61; range in {out <- i * i}close(out)
}
func printer(in <-chan int) {for i :&#61; range in {fmt.Println(i)}
}func main() {ch1 :&#61; make(chan int)ch2 :&#61; make(chan int)go counter(ch1)go squarer(ch2, ch1)printer(ch2)
}
其中&#xff0c;
1.chan<- int
是一个只写单向通道&#xff08;只能对其写入int类型值&#xff09;&#xff0c;可以对其执行发送操作但是不能执行接收操作&#xff1b;
2.<-chan int
是一个只读单向通道&#xff08;只能从其读取int类型值&#xff09;&#xff0c;可以对其执行接收操作但是不能执行发送操作。
在函数传参及任何赋值操作中可以将双向通道转换为单向通道&#xff0c;但反过来是不可以的。
2. 通道总结
关闭已经关闭的channel
也会引发panic
。