go语言的select调度器的用法
原理
select调度是使用了case来实现非阻塞式的通信,普通的通信是向channel中发送了数据,就必须有人接收,如果还没接收就再发送,就会阻塞住。
而case语句的特点就是如果不满足后面语句这个条件,就会继续判断下一个case语句,所以将收发数据的语句放在case中,这样就避免了阻塞。
代码实例
//先定义一个生成器,生成一个不断发送数据的channel
func produce() chan int{
out := make(chan int)
go func(){
for{
time.Sleep(5 * time.Second)
out <- 0
}
}()
return out
}
//然后在main函数中创建两个channel接收数据
func main(){
c1,c2 := produce(),produce()
//这样就会接收一次,如果都还没有发送数据就会走default,否则就会从c1或c2中接收一个数据,然后就会退出
select{
case n := <- c1:
fmt.Println(n)
case n := <- c2:
fmt.Println(n)
default:
fmt.Println(not recived data)
}
}
//也可以写一个循环持续接收
func main(){
c1,c2 := produce(),produce()
for{
select{
case n := <- c1:
fmt.Println(n)
case n := <- c2:
fmt.Println(n)
default:
fmt.Println(not recived data)
}
}
}
扩展
利用select调度的特点,简单地实现了非阻塞式的通信。在此基础上,我们还可以对其进行扩展,增加一些功能,同时保持非阻塞的性质。
例如
- 扩展1:接收到数据后不是直接输出,而是继续发送给另一个channel
- 扩展2:创建一个定时器,一定时间过后退出程序
- 扩展3:创建个定时器,每隔一段时间输出内容
扩展1
定义生成器,创建一个接收数据的channel
func createRecChan() chan int{
out := make(chan int)
go func(){
//这种写法会依次取完channel中的数据,如果channel中没有数据就停止接收
for i := range out{
fmt.Println(i)
}
}()
return out
}
main函数中需要修改一下
//1. 简单粗暴的修改
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
for{
select{
case n := <- c1:
w <- n
case n := <- c2:
w <- n
default:
fmt.Println(not recived data)
}
}
}
//这样存在的问题
//不能够继续非阻塞式的通信了
//假设第一次循环执行的是第一个case语句,会向w中发送一次数据,如果w的数据还没有被接收就到了第二次循环,还是执行的第一个case语句,这时又往w发送数据,这样就会阻塞住
//2. 再稍微修改一下
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
//将向w发送数据的动作也写成一个case语句,这样就避免了阻塞
//将n提出来定义,这样向w发送数据才能用到n
n := 0
for{
select{
case n = <- c1:
case n = <- c2:
case w <- n:
default:
fmt.Println(not recived data)
}
}
}
//这样存在的问题
//如果没有从c1,c2中接收到数据,n还是有值的,就会一直将当前n的值发送给w
//所以需要加一个判断,当从c1或c2中接收到数据了,才向w发送数据
//3. 再再稍微修改一下
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
n := 0
//定义一个标记,根据这个标记来判断是否接收了数据,初始赋为false
isHaveData := false
for{
//每一次循环定义一遍,然后再根据标记判断是否将w赋给实际要发送的channel
//外面定义的话赋一次值,后面就一直有值了,不合理
var actualChan chan int
if isHaveData{
actualChan = w
}
select{
case n = <- c1:
isHaveData = true
case n = <- c2:
isHaveData = true
case actualChan <- n:
isHaveData = false
default:
fmt.Println(not recived data)
}
}
}
//这样还是存在问题
//因为每次从c1或c2接收到数据后就会更改n的值,如果没有及时将n发送出去,就又接收到了数据,
//这样n的值就被改变了,导致会丢失很多数据
//可以使用一个slice来存储接收到的数据
//4. 再再再稍微修改一下
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
n := 0
//定义一个slice存放接收到的数据
var value []int
for{
//每一次循环定义一遍,然后再根据标记判断是否将w赋给实际要发送的channel
//外面定义的话赋一次值,后面就一直有值了,不合理
var actualChan chan int
//不适用标记了,而是直接判断slice的长度来判断是否接收到了数据
if len(value){
actualChan = w
//每次发送slice中第一个数据
actualValue = value[0]
}
select{
//每次接收到数据存到slice中
case n = <- c1:
value = append(value,n)
case n = <- c2:
value = append(value,n)
//发送数据后,从slice中取出
case actualChan <- actualValue:
value = value[1:]
default:
fmt.Println(not recived data)
}
}
}
扩展2
定时机制,超过一定时间退出程序
使用time.After方法
用法
time.After(time.Duration)返回的是一个channel,当经过一定时间后向该channel发送数据(只发送一次)。
通过这个特性,我们可以先创建一个channel,然后select调度器中从这个channel中接收数据,接收到了说明经过了设定好的时间,就可以退出程序了
代码
//在之前代码基础上添加
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
n := 0
//定义一个slice存放接收到的数据
var value []int
//创建一个定时器channel,10秒后发送数据
end := time.After(10 * time.Second)
for{
//每一次循环定义一遍,然后再根据标记判断是否将w赋给实际要发送的channel
//外面定义的话赋一次值,后面就一直有值了,不合理
var actualChan chan int
//不适用标记了,而是直接判断slice的长度来判断是否接收到了数据
if len(value){
actualChan = w
//每次发送slice中第一个数据
actualValue = value[0]
}
select{
//每次接收到数据存到slice中
case n = <- c1:
value = append(value,n)
case n = <- c2:
value = append(value,n)
//发送数据后,从slice中取出
case actualChan <- actualValue:
value = value[1:]
//接收到数据,说明时间到了,退出程序
case <- end
fmt.Println("good bye")
return
default:
fmt.Println(not recived data)
}
}
}
扩展3
定时机制,间隔一定时间发送数据
使用time.Tick方法
用法
time.Tick(time.Duration)返回的是一个channel,每间隔一定时间后向该channel发送数据(不只发送一次)。
通过这个特性,我们可以先创建一个channel,然后select调度器中从这个channel中接收数据,接收到了说明经过了设定好的时间
代码
//在之前代码基础上添加
func main(){
c1,c2 := produce(),produce()
w := createRecChan()
n := 0
//定义一个slice存放接收到的数据
var value []int
//创建一个定时器channel,10秒后发送数据
end := time.After(10 * time.Second)
//创建一个定时器channel,每秒后发送一次数据
tick := time.Tick(time.Second)
for{
//每一次循环定义一遍,然后再根据标记判断是否将w赋给实际要发送的channel
//外面定义的话赋一次值,后面就一直有值了,不合理
var actualChan chan int
//不适用标记了,而是直接判断slice的长度来判断是否接收到了数据
if len(value){
actualChan = w
//每次发送slice中第一个数据
actualValue = value[0]
}
select{
//每次接收到数据存到slice中
case n = <- c1:
value = append(value,n)
case n = <- c2:
value = append(value,n)
//发送数据后,从slice中取出
case actualChan <- actualValue:
value = value[1:]
//接收到数据,说明时间到了,退出程序
case <- end
fmt.Println("good bye")
return
//接收到数据,说明时间又过去了1秒钟
case <- tick
fmt.Println("又过了1秒")
default:
fmt.Println(not recived data)
}
}
}