接下面我们先来学习一下range在goroutine中的使用方法
下面看一下例子。
package main import ( "fmt" "time" ) func sample(message chan string) { //形参为chan string类型 message <-"hello goroutine!1" message <-"hello goroutine!2" message <-"hello goroutine!3" message <-"hello goroutine!4" //通道数量为3,但是往通道中写入了4条数据 } func sample2(message chan string) {//形参为chan string类型 str:=<-message//第二个goroutine从通道中取出字符串 str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串 message<-str //将新的字符串重新写入到message通道中 } func main() { //创建通道,用到参数传递方式,最好还是放在和goroutine一起 var message = make(chan string,4) //通道类型后面设置要开几条通道的数量,整数的 go sample(message) //将通道传递进去,函数再设置形参 go sample2(message)//将通道传递进去,函数再设置形参 time.Sleep(3*time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return了 //现在我们用range来取数据 for str:=range message{ fmt.Println(str) } fmt.Println("helloworld!") }
下面的代码运行肯定报错,我故意这么写的。
hello goroutine!4
hello goroutine!3
fatal error: all goroutines are asleep - deadlock! 说明:所有的goroutine都取完了。怎么还在取,死了
hello goroutine!2
hello goroutine!1,i am goroutine!
因为上面的通道取完了后通道为空了。但是for range在从通道中取数据的时候他不知道是空的。所以空了也在取,所以应该在数据发送完毕后关闭通道
应该在哪里呢。根据不同的情况,处理方法都不一样,针对上面的代码。我们发现先进先出的原则,goroutine1先出来了,并且经过拼接字符串并输出了。所以我们应该在拼接后就马上关闭通道
func sample2(message chan string) {//形参为chan string类型 str:=<-message//第二个goroutine从通道中取出字符串 str=str+",i am goroutine!" //将取出的字符串拼接成新的字符串 message<-str //将新的字符串重新写入到message通道中 close(message) //关闭通道 }
关闭后再运行就正常了
现在我们来说一下select随机选择的功能:
在channel的写入一方,有可能出现有多个goroutine往里写
很有可能出现goroutine内部在往里写之前要做一些处理,比如说网络拉取一些文件,网络请求一端的API会有一些延迟。
怎么在消费的一端做合理的控制,这时我们用select等待机制就派上用场了
现在说一下随机选择机制
如果我们有多个通道呢。就要用到select的随机选择机制了
看下面代码
package main import ( "strconv" "time" "fmt" ) //循环执行20次输出0-19到通道ch中 func sample(ch chan string) { //形参为chan string类型 for i := 0; i < 20; i++ { ch <- "i am ch1:" + strconv.Itoa(i) time.Sleep(1 * time.Second) //写入通道睡1秒,要不和下面的通道就乱了,不好区分 } } //循环输出0-9到通道中 func sample2(ch chan int) { //形参为chan int类型 for i := 0; i < 10; i++ { ch <- i time.Sleep(2 * time.Second) //这个也睡2秒,两个通道交替着来。这个你随意 } } func main() { //创建2个通道,作用是两个通道同时写和取数据 var ch1 = make(chan string, 3) //通道类型后面设置要开几条通道的数量,整数的 var ch2 = make(chan int, 5) //通道类型后面设置要开几条通道的数量,整数的 for i := 0; i < 10; i++ { //同时10个goroutine一起执行往通道中写数据 go sample(ch1) //将通道传递进去,函数再设置形参 go sample2(ch2) //将通道传递进去,函数再设置形参 } for i:=0;i<1000;i++{ //循环从通道中取数据,否则只能取一次。 这样可以不停的从通道中取数据。只要有条件数据。就判断一下下面哪个通道中有数据 //就选择哪个case条件 //现在用select来读取通道中的数据 select { case str1, ch1ok := <-ch1://取出来的数据放str1,ch1ok是判断是否有数据,true是有,false没有 if !ch1ok {//如果没有数据就输出ch1 false fmt.Println("ch1 false") } else {//否则打印数据 fmt.Println(str1) } case str2, ch2ok := <-ch2://也是一样。从通道中取出来数据放str2中,然后判断有没有数据 if !ch2ok {//如果没数据就输出ch2 false fmt.Println("ch2 false") } else {//否则输出打印数据 fmt.Println("\t",str2) } } } time.Sleep(30 * time.Second) //这里需要睡3秒,否则main goroutine直接就输出第一个goroutine的内容就直接return了 }
运行结果:看出来了。ch1,ch2都是交替往通道里写数据的
i am ch1:0
i am ch1:0
0
0
0
0
0
i am ch1:0
0
i am ch1:0
0
0
i am ch1:0
i am ch1:0
0
0
i am ch1:0
i am ch1:0
i am ch1:0
i am ch1:0
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
i am ch1:1
1
1
1
1
1
1
1
1
i am ch1:2
1
1
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:2
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
i am ch1:3
2
2
2
2
2
2
2
2
2
2
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:4
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
i am ch1:5
3
3
3
3
3
3
3
3
3
3
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:6
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
i am ch1:7
下面看一下select等待机制
等待机制的好处是,一般如果从国外服务器取数据,是延迟非常高的,用time.sleep是没办法知道需要等待几秒钟的。但是如果用select就可以一直无限等待接收通道
我们来看下面代码就明白了
我们先来把sample和sample2里的time.sleep分别设置为3秒和60秒。用来测试select等待机制
package main import ( "strconv" "time" "fmt" ) //循环执行20次输出0-19到通道ch中 func sample(ch chan string) { //形参为chan string类型 for i := 0; i < 20; i++ { ch <- "i am ch1:" + strconv.Itoa(i) time.Sleep(3 * time.Second) //现在这个睡3秒写入通道一次 } } //循环输出0-9到通道中 func sample2(ch chan int) { //形参为chan int类型 for i := 0; i < 10; i++ { ch <- i time.Sleep(60 * time.Second) //60秒写入通道一次 } } func main() { //创建2个通道,作用是两个通道同时写和取数据 var ch1 = make(chan string, 3) //通道类型后面设置要开几条通道的数量,整数的 var ch2 = make(chan int, 5) //通道类型后面设置要开几条通道的数量,整数的 for i := 0; i < 10; i++ { //同时10个goroutine一起执行往通道中写数据 go sample(ch1) //将通道传递进去,函数再设置形参 go sample2(ch2) //将通道传递进去,函数再设置形参 } for { //用死循环,无限循环等待从ch中取数据 //就选择哪个case条件 //现在用select来读取通道中的数据 select { case str1, ch1ok := <-ch1://取出来的数据放str1,ch1ok是判断是否有数据,true是有,false没有 if !ch1ok {//如果没有数据就输出ch1 false fmt.Println("ch1 false") } else {//否则打印数据 fmt.Println(str1) } case str2, ch2ok := <-ch2://也是一样。从通道中取出来数据放str2中,然后判断有没有数据 if !ch2ok {//如果没数据就输出ch2 false fmt.Println("ch2 false") } else {//否则输出打印数据 fmt.Println("\t",str2) } } } //time.Sleep(30 * time.Second) //这里就不用睡了 }
运行结果:
可以看出。因为ch1睡3秒往通道中写一次数据,所以select肯定先从ch1中取数据,然后ch2是每隔60秒才往通道中写一次数据,所以ch2输出就比较慢,甚至最后是先把ch1取完后再输出ch2
i am ch1:17
i am ch1:17
i am ch1:17
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:18
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
i am ch1:19
1
1
1
1
1
1
1
1
1
1
2
2
2
2
2
2
2
2
2
2
3
3
3
3
3
3
3
3
3
3
4
4
4
4
4
4
4
4
4
4
输出结果很长。我就不粘贴了。大家可以自己回去测试一下。
select等待机制非常好。结合上channel非常适合go的协程开发。
好了。下一节课我们将讲解搭建一个网站。用来测试流量统计系统