首先我是从一个指导中看到一段话才想要来写这篇文章的,主要是一段容易让人误解的话,这里我直接把他的代码附上。也可以先看一些基础知识:
目录
基础
作用:用于在协程之间传递信息
类型:通道是引用类型,规则和切片相似,需要make
定义方式和操作:
var ch chan int//最后一个标识通道传递的元素类型
make(chan 元素类型, [缓冲大小])
ch <- n //写
n <- ch //读
close(ch)//关闭
误解代码
代码的核心是两个线程以及连个无缓冲的通道。误会主要发生在下面的依据注释中**通道关闭后再取值ok=false**,对于他设置的无缓冲通道交换信息来说,可以是对的,但如果是有缓冲通道,则是明显的错误。下面我附上稍微修改的代码,二者对比就可明白。
package main
//代码一
import (
"fmt"
//"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~100的数发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
for {
i, ok := <-ch1 // **通道关闭后再取值ok=false**
if !ok {
break
}
ch2 <- i * i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}
这是稍微修改的代码,可以看到修改了两处,溢出时把通道1换成了带缓冲的,一处时把关闭通道1的时刻换到i=50的时候,我们对比一下二者的执行结果:
修改代码和结果对比
package main
import (
"fmt"
//"time"
)
func main() {
ch1 := make(chan int,100)//改成了有缓冲的通道
ch2 := make(chan int)
// 开启goroutine将0~100的数发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
for {
i, ok := <-ch1 // 通道关闭后再取值ok=false
if !ok {
break
}
//修改处
if i == 50{
close(ch1)
}
ch2 <- i * i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}
可以看到,虽然提前关闭了通道,但是因为有缓冲区的缘故,字符都是正常取出的,因为
i, ok := <-ch1 只要通道里有值存在返回的就是ok。关闭后虽然无法添加数据,但是通道取值是正常的,只有当元素被取完后才会返回false。其实他这么说能行得通的原因是无缓冲通道的原因,接受通道会一直阻塞知道发送方传值,通道中是不存有元素的。因此如果没有接收,不可以向一个无缓冲通道添加值。
单向通道
有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。
Go语言中提供了单向通道来处理这种情况:
可以在函数传参及任何赋值操作中将双向通道转换为单向通道。
package main
import "fmt"
//写通道
func counter(out chan<- int) {
for i := 0; i < 100; i++ {
out <- i
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for i := range in {
out <- i * i
}
close(out)
}
//读通道
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go counter(ch1)
go squarer(ch2, ch1)
printer(ch2)
}