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)

不要通过共享内存来通信;通过通信来共享内存

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值