bilibili视频上看的一个面试题, 看了很久, 也想了很久, 自己平时都是做web后端开发,很少接触到并发编程, 最后困难的实现了一下:

描述: 实现一个负载器, 可以注册多个worker工作, 每个worker的工作能力不同, lb也不能阻塞. 哔哩哔哩网址

package test_dir

import (
	"fmt"
	"testing"
	"time"
)
var workerQueue = make(chan *worker)

type loadBalance struct {
	tasks  chan func(int)
}

type worker struct {
	id    int
	queue chan func(int)
}

func (w *worker) work() {
	for t := range w.queue {
		t(w.id)
	}
}

func (w *worker) submit(task func(int)) {
	w.queue <- task
}

func (l *loadBalance) submit(task func(int)) {
			go func() { l.tasks <- task }()   //不可阻塞LB
}

func (l *loadBalance) wait() {
	go func() {
		for {
			w := <-workerQueue // 获取一个worker, 没有可用的会阻塞在这
			f := <-l.tasks // 获取一个task任务, 没有也会阻塞
			go func() {
				w.submit(f) // 提交任务给worker, 如果塞不进去会阻塞当前worker重回workerQueue
				workerQueue <- w
			}()
		}
	}()
	select {}
}

// 注册负载
func LB(workerPowerNum ...int) *loadBalance {
	for k, v := range workerPowerNum {
		worker := &worker{queue: make(chan func(int), v), id: k + 1}
		// 添加到ch
		go func() { workerQueue <- worker }()
		go worker.work()
	}
	return &loadBalance{tasks: make(chan func(int))}
}

func Test_LB(t *testing.T) {
	lb := LB(4, 1, 2, 5, 6)
	for i := 0; i < 20; i++ {
		lb.submit(func(v int) func(int) {
			return func(i int) {
				time.Sleep(time.Second)
				fmt.Println("任务执行完成: ", v, "机器编号", i)
			}
		}(i))
	}
	lb.wait()
}

输出:

=== RUN   Test_LB
任务执行完成:  0 机器编号 2
任务执行完成:  1 机器编号 3
任务执行完成:  4 机器编号 1
任务执行完成:  2 机器编号 4
任务执行完成:  3 机器编号 5
任务执行完成:  19 机器编号 3
任务执行完成:  12 机器编号 5
任务执行完成:  18 机器编号 2
任务执行完成:  11 机器编号 1
任务执行完成:  16 机器编号 1
任务执行完成:  17 机器编号 2
任务执行完成:  5 机器编号 5
任务执行完成:  13 机器编号 1
任务执行完成:  6 机器编号 5
任务执行完成:  14 机器编号 1
任务执行完成:  7 机器编号 5
任务执行完成:  15 机器编号 1
任务执行完成:  8 机器编号 5
任务执行完成:  9 机器编号 5
任务执行完成:  10 机器编号 5

自己跑测试结果能基本符合,也不知道是不是符合题意, 但是视频最后博主说要实现LB和worker的状态分离: 我自己用了第三个变量workerQueue ::>_<::,技穷了, 不要暴力检测的: 各种锁什么的临界, 大家可以各自实现一下完美需求.

最后发现用select{}阻塞进程会报死锁异常, 不知到大家为啥不会报死锁~~