Go语言中的Select调度

首先我们根据实例来讲解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值),这样数据就不准确了。

记录贴, 请改正。

猜你喜欢

转载自my.oschina.net/90design/blog/1818657