这是我参与「第三届青训营 -后端场」笔记创作活动的第二篇笔记
channel体现了go语言设计者的核心思想之一,即通过通信来共享内存,而不是通过共享内存来实现通信。即使channel仍然是属于第二节的内容,但是它的重要性并不会因为因此降低。
在java中实现通信有非常典型的"flag"标志,比如:
boolean flag = false;
java通过在不同的文件中来传递和修改flag的值,这就是通过共享内存来实现通信。
下面我们来看看go语言是怎么实现通信的。
channel快速入门
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("Worker %d received %c\n",
id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
//以chan为对称点,chan<-是向chan发数据,<-chan是某个变量要收数据
func chanDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
channels[i] = createWorker(i)
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
func bufferedChannel() {
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
func channelClose() {
c := make(chan int)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}
func main() {
fmt.Println("Channel as first-class citizen")
chanDemo()
fmt.Println("Buffered channel")
bufferedChannel()
fmt.Println("Channel close and range")
channelClose()
}
首先谈一下channel的基本用法。首先在chanDemo函数中,我们发现以chan为对称点,chan<-是向channel发数据,<-channel是某个变量要从channel收数据。channel可以不规定方向,但如果规定方向的话,就不能使用相反方向的channel。
在createWorker函数中,我们发现channel和函数一样都是“一等公民”,可以作为参数传递,亦可以作为参数返回,这是go语言非常优秀的地方。
createWorker函数创建了10个channel,这10个channel又各自开了协程,准备接收数据。如果接收到了数据,就打印一句话,比如“Worker 0 received a”。注意如果有接受数据的channel,那么就必须有发送数据的channel,否则就会死锁。
另外,createWorker函数的写法就是快速返回一个channel,但必须在中间开一个协程go func(),在协程中去处理工作,不然channel收不到数据还是会死锁。
创建完channel后,每个channel[i]收到对应的数据并打印出来。
在bufferedChannel函数中,我们发现可以为channel设计缓冲区,channel收到前三个数据先不打印,等到收到第四个数据的时候再打印。当然还是要注意要让主线程停一下,否则程序直接结束了。
最后在channelClose函数中,如果我们确定channel的数据发送方有一个结尾,可以调用channel.close(),但是channel的接收方不能主动关闭,只能一直接收0。
因此在worker函数中使用for n := range c,如果数据接受完了就自动退出。
今天就分享到这里,有什么疑问大家可以到评论区交流讨论