首先我们根据实例来讲解select的使用。(通过通信来共享内存)
问题: 假设我们有一个煤矿(煤老板), 手下有两个工人,一辆卡车负责运输煤矿,现在你需要将所有煤矿在最快的时间内挖完。并且实时查看他们的工作状况。
首先我们先来“创建”工人:
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(150)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
那么工人会每隔一段时间挖出一定量的煤矿数据,等待被运走。
注意: 此处我们另起了一个协程, 防止发送时,没有接收者被堵塞。
那么下面我们来“招聘”司机负责运输,先把司机的条件罗列出来:
首先呢,就是必须有运输的技能才可以。
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("work id %d received %d \n", id, n)
}
}
创建一个司机/卡车:
func createWork(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
所有条件准备完毕,开始我们的挖煤工作:
1. 创建两个工人, 一辆卡车
var c1, c2 = generator(), generator()
w := createWork(0)
2. 开始运输工作:
思路:开始挖煤工作,两个工人同时开始挖煤工作, 如果挖到 立刻装进卡车。
var c1, c2 = generator(), generator()
w := createWork(0)
n := 0
for {
select {
case n = <-c1: // 此处n 是为了查看方便,其他可以直接w<- <-c1
w <- n
fmt.Println("收到c1数据:", n)
case n = <-c2:
w <- n
fmt.Println("收到c2数据:", n)
}
}
没有直接发送给w也是减轻接收者的压力。
运行结果:
如果发送方的速度比接收方快,那么接收方还没来的急消费,就把原数据覆盖了,所以此处的n定义是错误的。
那么,我们应该现将数据存储下来,等待接受方慢慢处理。[]int方式存储。
var val []int
for {
select {
case n := <-c1:
val = append(val, n)
case n := <-c2:
val = append(val, n)
}
}
如果发送方远超于接受方能力, 那么[]int数据就行积压过多,我们通过time包,来检测数据的积压情况。
var val []int
tick := time.Tick(time.Second)
for {
var activeW chan<- int
var activeV int
if len(val) > 0 {
activeW = w
activeV = val[0]
}
select {
case n := <-c1:
val = append(val, n)
case n := <-c2:
val = append(val, n)
case <-tick: // 挤压检测
fmt.Println("queue len = : ", len(val), " ", val)
}
}
time.Tick会返回一个chan bool类型的通道,每隔一段时间,就会接受到数据。
如果数据接收过多, 某个数据的处理时间过长,可能引起服务的崩溃或者缓慢, 在允许的范围下,我们应该设置每个数据的处理时长,限制一定时间内处理完成。
并且,如果积攒的数据过多未处理,则会导致服务长时间运行在高频率工作下。通过限制执行时间,来约束服务所处理的数据量。
var val []int
tm := time.After(10 * time.Second)
for {
var activeW chan<- int
var activeV int
if len(val) > 0 {
activeW = w
activeV = val[0]
}
select {
case n := <-c1:
val = append(val, n)
case n := <-c2:
val = append(val, n)
case <-time.After(80 * time.Millisecond): //超时操作
fmt.Println("timeout")
case <-tm: // 执行一段时间
fmt.Println("bye")
return
}
}
time.After 定义在for循环之外,而且只被初始化一次,所以当到达时间限制时,自动断开。 如果time.After定义在for之内, 每次循环都会初始化一次, 也就是每次处理的时长。
最后完整代码:
package main
import (
"fmt"
"math/rand"
"time"
)
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(150)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("work id %d received %d \n", id, n)
}
}
func createWork(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
var c1, c2 = generator(), generator()
w := createWork(0)
// 防止接收慢,发送覆盖了之前未接受的数据
var val []int
tm := time.After(10 * time.Second)
tick := time.Tick(time.Second)
for {
var activeW chan<- int
var activeV int
if len(val) > 0 {
activeW = w
activeV = val[0]
}
select {
case n := <-c1:
val = append(val, n)
case n := <-c2:
val = append(val, n)
case activeW <- activeV: // nil channel select是不通过(阻塞)
val = val[1:]
case <-time.After(80 * time.Millisecond): //超时操作
fmt.Println("timeout")
case <-tick: // 挤压检测
fmt.Println("queue len = : ", len(val), " ", val)
case <-tm: // 执行一段时间
fmt.Println("bye")
return
}
}
}
因为select 在调度到nil channel时,是不能通过的,所以将w 赋值给未初始化的activeW ,在没有数据之前, activeW 是一个nil channel, 所以无法向nil channel发送数据,case 无法通过。
上面我们提到, 如果直接往w 发送数据, 在没有接受到 c1 c2发送来的数据之前,case 在w<-n 通过,那么接受到的就是0值( nil值),这样数据就不准确了。
记录贴, 请改正。