接下面我们先来学习一下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的协程开发。

好了。下一节课我们将讲解搭建一个网站。用来测试流量统计系统