GO CHANNEL基础用法
基本用法
声明一个channel
//这样是声明一个空的chan,这样声明的chan目前我们是没法用的,但是他也能用,这个我们后面再讲
var c chan int // c == nil
那我们来生成一个可用的channel
c := make(chan int)
这样声音的channel就可以用了
那我们来写一个方法,看看channel怎么用
package main
import "fmt"
func chanDemo(){
c := make(chan int)
c <- 1
c <- 2
n := <-c
fmt.Println(n)
}
func main(){
chanDemo()
}
然后运行:
发现报错了
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
**fatal error: all goroutines are asleep - deadlock!**
goroutine 1 [chan send]:
main.chanDemo()
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:10 +0x59
main.main()
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:17 +0x20
Process finished with exit code 2
死锁在
c <- 1
因为channel是gorouting到gorouting的交互,我们必须要采用另外一个gorouting去收他,这发了一个数据没人收是会deadlock(死锁)的,因为我们需要有一个人来负责收,那么怎么收呢,我们要在c接收之前开一个gorouting
package main
import "fmt"
func chanDemo(){
c := make(chan int)
//1:这里加一个gorouting来去收c,一定要加在c <- 1之前先创建接收者,不然还是会报错
go func(){
for{
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
}
func main(){
chanDemo()
}
然后我们运行,发现只打出了一个1
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
1
这是因为我们把2发送给gorouting的时候main已经执行结束了,gorouting还来不及执行,所以这里只打印了1出来。所以为了让他打印完,我们先在chanDemo后面加一个time.Sleep(time.Millisecond)让他执行完。这里有一点要和大家说一下,这一句只是为了让最后一个c <-2执行完,并不是说这一毫秒是让整个程序执行完。因为程序从上往下按顺序执行,只有执行到最后一句的时候c <- 2才需要这一毫秒的时间,让程序执行完他,而不会因为main函数的退出,而没来得及执行。那么我们现在的程序就变成了:
package main
import (
"fmt"
"time"
)
func chanDemo(){
c := make(chan int)
//1:这里加一个gorouting来去收c,一定要加在c <- 1之前先创建接收者,不然还是会报错
go func(){
for{
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
//2:这一句让程序sleep1毫秒是为了让c <- 2执行完
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
然后程序就能输出1和2了
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
1
2
函数式用法
接下来这要讲的这件事呢,和函数式编程有点象,go语言函数是一等公民,我们知道go语言函数可以作为参数,可以作为返回值,他很有用。那么channel呢,他也是一等公民,也可以作为参数,作为返回值。
其实我们看到:
//chanDemo中的代码片断
c:=make(chan int)
go func(){
for{
n := <-c
fmt.Println(n)
}
}()
chanDemo 中的go func(){}() 这一段创建gorouting的方法中的<-c其实就是外面c:=make(chan int)中的c,他其实就是相当于一种参数,但是因为我们go func(){}()是一个匿名函数,他是一个闭包,他闭包里引用了这个c,那我们不要使用他匿名函数的写法,我们把他变成一个正常的函数,我们把他提到方法外面去,我们传一个chan给他:
package main
import (
"fmt"
"time"
)
func worker(c chan int){
for{
n := <-c
fmt.Println(n)
}
}
func chanDemo(){
c := make(chan int)
//1:这里加一个gorouting来去收c,一定要加在c <- 1之前先创建接收者,不然还是会报错
go worker(c)
c <- 1
c <- 2
//2:这一句让程序sleep1毫秒是为了让c <- 2执行完
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
那么他还是打印出了1,2
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
1
2
那么channel可以作为参数,那么我们这里就可以传更多的参数进来,比如我们再传入一个id,给worker加上id int:
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int){
for{
//这里Println改成Printf
fmt.Printf("Worker %d received %d\n",id, <-c)
}
}
func chanDemo(){
c := make(chan int)
//1:这里加一个gorouting来去收c,一定要加在c <- 1之前先创建接收者,不然还是会报错
go worker(0, c) //3.这里加上第一个参数0作为id
c <- 1
c <- 2
//2:这一句让程序sleep1毫秒是为了让c <- 2执行完
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
运行一下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received 1
Worker 0 received 2
那么接下来,我们可以开很多个worker,那我们来一口气开10个worker
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int){
for{
//这里Println改成Printf
fmt.Printf("Worker %d received %d\n",id, <-c)
}
}
func chanDemo(){
for i := 0; i < 10; i++ {
c := make(chan int)
go worker(0, c)
c <- 1
c <- 2
}
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
输出:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received 1
Worker 0 received 2
Worker 1 received 1
Worker 2 received 1
Worker 3 received 1
Worker 4 received 1
Worker 5 received 1
Worker 6 received 1
Worker 7 received 1
Worker 8 received 1
Worker 9 received 1
Worker 4 received 2
Worker 3 received 2
Worker 7 received 2
Worker 5 received 2
Worker 6 received 2
Worker 8 received 2
Worker 9 received 2
Worker 1 received 2
Worker 2 received 2
这里我们看到输出顺序很乱,是因为gorouting是一个协程,他的执行是由gorouting随机调度的,并不是按顺序执行。
下面我想把每个channels都保存下来,然后把数据分发给10个worker,那么,我们可以创建一个大小为10的数组,并给这10个数组发送数据,我们这里就分发字母吧,把fmt.Printf最后一个%d改成%c
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int){
for{
//这里Println改成Printf
fmt.Printf("Worker %d received %d\n",id, <-c)
}
}
func chanDemo(){
var channels [10]chan int
for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go worker(i, channels[i])
}
for i := 0; i < 10; i++{
channels[i] <- 'a' + i
}
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
运行一下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 6 received g
Worker 2 received c
Worker 5 received f
Worker 3 received d
Worker 1 received b
Worker 7 received h
Worker 0 received a
Worker 4 received e
Worker 8 received i
Worker 9 received j
我们每个人都有一个channel,并将他们进行分发,分发后以后,每个gorouting会把收到的信息打印出来。比如说我们收一个不够,我们想再收一个, 这次我们打个大写的
//前面代码不变,不重复贴出来了
func chanDemo(){
var channels [10]chan int
for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go worker(i, channels[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)
}
再打印一下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received a
Worker 0 received A
Worker 7 received h
Worker 8 received i
Worker 6 received g
Worker 9 received j
Worker 4 received e
Worker 3 received d
Worker 5 received f
Worker 2 received c
Worker 1 received b
Worker 1 received B
Worker 5 received F
Worker 4 received E
Worker 6 received G
Worker 2 received C
Worker 7 received H
Worker 3 received D
Worker 9 received J
Worker 8 received I
现在有小写的大写的都打印出来了。
我们前面是用数组来创建channel,我们还可以把channel作为返回值。我们不要让主程序来创建channel,我们自己来建.
package main
import (
"fmt"
"time"
)
//这里是一个标准的创建chan的写法
//这个函数做了什么事情呢,他并没有做什么事情,他只是创建了一个channel,然后开了一个gorouting,然后立即就返回了,所以他并没有做事情,真正做事情的是在开的这个gorouting里面
//所以我们把他改名为createWorker
func createWorker(id int) chan int{
//我们在这里自己建一个channel
c := make(chan int);
go func() { //这里一定要是一个gorouting,不然for在这里一直跑就程序死循环了
for{
//这里Println改成Printf
fmt.Printf("Worker %d received %c \n",id, <-c)
}
}()
return c
}
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 main(){
chanDemo()
}
然后运行一下,我们发现结果还是对的:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 8 received i
Worker 5 received f
Worker 6 received g
Worker 7 received h
Worker 1 received b
Worker 3 received d
Worker 9 received j
Worker 0 received a
Worker 0 received A
Worker 1 received B
Worker 4 received e
Worker 2 received c
Worker 2 received C
Worker 5 received F
Worker 3 received D
Worker 4 received E
Worker 8 received I
Worker 9 received J
Worker 7 received H
Worker 6 received G
那我们在这个channel的定义上还可以进一步的进行修饰,我们这个createWorker返回的是一个channel int,那么这个channel到底应该怎么用呢,我们很难看出来,当然现在程序比较简单,我们还是能看出来的,但程序复杂之后,就很难看出来了,因为,我们应该告诉其它来调用我们的人,我们定义的这个channel应该怎么来用,那么可以从
channels[i] <- 'A' + i
看到,我们的channel是用来发数据的,因此我们可以把<-指到chan后面,比如chan<-,代码如下:
func createWorker(id int) chan<- int{
这样很就形象了,我们这个channel是用来收数据的,因此用这个channel的人,只能给他来发数据。
既然外面的人只能发数据,那我里面的人只能收数据:
fmt.Printf("Worker %d received %c \n",id, <-c)
那么我们整个代码改造如下:
package main
import (
"fmt"
"time"
)
func createWorker(id int) chan<- int{ //<---这里
//我们在这里自己建一个channel
c := make(chan int);
go func() { //这里一定要是一个gorouting,不然for在这里一直跑就程序死循环了
for{
//这里Println改成Printf
fmt.Printf("Worker %d received %c \n",id, <-c)
}
}()
return c
}
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 main(){
chanDemo()
}
并且,如果我们如果箭头写错了,编译器还会报错
接下来我们介绍另一种channel的另一种用法buffedChannel
bufferedChannel用法
package main
func bufferedChannel(){
c := make(chan int)
c <- 1
}
func main(){
bufferedChannel()
}
这是之前我们最开始报错的代码,这样写为什么会报错呢,因为我们没有人来接收,因为我们chan一旦发数据,就必须要有人来收。
此代码运行结果如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.bufferedChannel(...)
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:5
main.main()
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:9 +0x50
虽然我们是轻量级的,但是我们发数据就要进行等人来收呢,也是比较耗资源,那么我们可以来加入一个缓冲区
c := make(chan int, 3)
这样我们就可以往里面发3个数
package main
func bufferedChannel(){
c := make(chan int, 3) //<--注意这里,填了第二个参数3
c <- 1
c <- 2
c <- 3
}
func main(){
bufferedChannel()
}
运行结果如下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Process finished with exit code 0
虽然没有打印,但是我们也可以发现,这次运行并没有报错,因为我们的缓冲区大小是3,
我们再加一个参数
package main
func bufferedChannel(){
c := make(chan int, 3)
c <- 1
c <- 2
c <- 3
c <- 4 //<--多加一个参数4
}
func main(){
bufferedChannel()
}
运行结果如下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.bufferedChannel(...)
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:8
main.main()
/Users/lijiayi/mygo/src/learngo/channel/test/testchannel2.go:12 +0xa7
Process finished with exit code 2
因为我们缓冲区的大小只有3。
那么现在我们来把他打印出来,同样也需要用到time.Sleep
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
for{
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}
func bufferedChannel(){
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
func main(){
bufferedChannel()
}
运行结果:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received a
Worker 0 received b
Worker 0 received c
Worker 0 received d
虽然结果都是一样的,但是channel在创建的时候加上第二个参数,那么channel就有一个缓冲区,这个在提升性能上是有一定的优势的。
那么,我们发现我们现在的channel并不知道数据什么时候发完了,我们还是有一种方法是可以告诉我们的接收方数据是发完了的。那我们来看一下:
channelClose的用法
如果我们数据有一个明显的结尾的话呢,我们是可以把chan给close的,那么这个close永远都是发送方来close。发送方通过close来通知接收方,我没有新的数据要发了。
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
for{
fmt.Printf("Worker %d received %c\n", id, <-c)
}
}
func channelClose(){
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}
func main(){
channelClose()
}
运行结果如下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received a
Worker 0 received b
Worker 0 received c
Worker 0 received d
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 received
Worker 0 receive
我们会发现,后面多了很多空的数据,看上去象死掉了一样,这是因为我们channel一旦close,我们接收的人还是会收到数据的,那么close一时关闭之后,他收到的是什么呢,因为我们是用%c打印的字符串,如果我们用%d打印字符串,我们就会发现,打印的是0,如果是字符串,其实就是空字符串。
我们怎么来判断他发的是空串呢,我们用2个变量来收:
n, ok := <-c
完整代码如下:
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
for{
n, ok := <-c
if ok {
fmt.Printf("Worker %d received %c\n", id, n)
}else {
break;
}
}
}
func channelClose(){
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c)
time.Sleep(time.Millisecond)
}
func main(){
channelClose()
}
运行如下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 0 received a
Worker 0 received b
Worker 0 received c
Worker 0 received d
我们除了用if的方法来判断呢,我们还有一种方法,使用for … range来接收
func worker(id int, c chan int) {
for{
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
}
}
那么运行结果也是一样的。
那么我们如果不close掉channel,程序会是什么样的呢,我们用第一个demo来跑一遍,并把worker改成for … range:
package main
import (
"fmt"
"time"
)
func worker(id int, c chan int) {
for{
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;
}
func chanDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
channels[i] = createWorker(i)
//channels[i] <- 1
//channels[i] <- 2
}
for i :=0; i< 10; i++ {
channels[i] <- 'a' + 1
//channels[i] <- 'A' + 2
}
for i :=0; i< 10; i++ {
channels[i] <- 'a' + 1
//channels[i] <- 'A' + 1
}
//fmt.Println(n)
time.Sleep(time.Millisecond)
}
func main(){
chanDemo()
}
运行结果如下:
/private/var/folders/sm/5dbnk3792f9bsy9sp7l6y7780000gp/T/___go_build_testchannel2_go #gosetup
Worker 3 received b
Worker 5 received b
Worker 7 received b
Worker 4 received b
Worker 1 received b
Worker 2 received b
Worker 0 received b
Worker 0 received b
Worker 5 received b
Worker 1 received b
Worker 2 received b
Worker 3 received b
Worker 4 received b
Worker 8 received b
Worker 9 received b
Worker 6 received b
Worker 6 received b
Worker 8 received b
Worker 7 received b
Worker 9 received b
也是会停止运行,这是为什么呢,这还是前面我们提到的,channel虽然没有停掉,所以收的人停不了,但是发的人所在的main函数执行完退出了,那么主进行生成的channel也会停止并退出,当然,程序中的接收方gorouting也跟着退出了,因此他也只收了sleep的那一毫秒。
channel理论基础
Communication Sequential Procss(CSP)
不要通过共享内存来通信;通过通信来共享内存